aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/activitypub/client.ts49
-rw-r--r--server/controllers/activitypub/inbox.ts6
-rw-r--r--server/controllers/api/accounts.ts13
-rw-r--r--server/controllers/api/config.ts22
-rw-r--r--server/controllers/api/index.ts4
-rw-r--r--server/controllers/api/jobs.ts2
-rw-r--r--server/controllers/api/overviews.ts2
-rw-r--r--server/controllers/api/server/debug.ts6
-rw-r--r--server/controllers/api/server/follows.ts5
-rw-r--r--server/controllers/api/server/logs.ts10
-rw-r--r--server/controllers/api/server/redundancy.ts84
-rw-r--r--server/controllers/api/server/stats.ts10
-rw-r--r--server/controllers/api/users/index.ts2
-rw-r--r--server/controllers/api/users/me.ts4
-rw-r--r--server/controllers/api/users/my-subscriptions.ts6
-rw-r--r--server/controllers/api/video-channel.ts3
-rw-r--r--server/controllers/api/video-playlist.ts1
-rw-r--r--server/controllers/api/videos/captions.ts2
-rw-r--r--server/controllers/api/videos/import.ts22
-rw-r--r--server/controllers/api/videos/index.ts7
-rw-r--r--server/controllers/client.ts2
-rw-r--r--server/controllers/static.ts16
-rw-r--r--server/controllers/tracker.ts15
-rw-r--r--server/controllers/webfinger.ts4
-rw-r--r--server/helpers/activitypub.ts81
-rw-r--r--server/helpers/audit-logger.ts24
-rw-r--r--server/helpers/core-utils.ts68
-rw-r--r--server/helpers/custom-jsonld-signature.ts90
-rw-r--r--server/helpers/custom-validators/activitypub/actor.ts4
-rw-r--r--server/helpers/custom-validators/activitypub/cache-file.ts2
-rw-r--r--server/helpers/custom-validators/activitypub/video-comments.ts2
-rw-r--r--server/helpers/custom-validators/activitypub/videos.ts27
-rw-r--r--server/helpers/custom-validators/misc.ts4
-rw-r--r--server/helpers/custom-validators/plugins.ts6
-rw-r--r--server/helpers/custom-validators/user-notifications.ts3
-rw-r--r--server/helpers/custom-validators/video-abuses.ts4
-rw-r--r--server/helpers/custom-validators/video-captions.ts2
-rw-r--r--server/helpers/custom-validators/video-imports.ts2
-rw-r--r--server/helpers/custom-validators/video-playlists.ts6
-rw-r--r--server/helpers/custom-validators/video-redundancies.ts12
-rw-r--r--server/helpers/custom-validators/videos.ts8
-rw-r--r--server/helpers/express-utils.ts16
-rw-r--r--server/helpers/ffmpeg-utils.ts191
-rw-r--r--server/helpers/logger.ts15
-rw-r--r--server/helpers/middlewares/videos.ts17
-rw-r--r--server/helpers/regexp.ts2
-rw-r--r--server/helpers/register-ts-paths.ts2
-rw-r--r--server/helpers/signup.ts2
-rw-r--r--server/helpers/utils.ts4
-rw-r--r--server/helpers/video.ts27
-rw-r--r--server/helpers/webtorrent.ts24
-rw-r--r--server/helpers/youtube-dl.ts61
-rw-r--r--server/initializers/checker-after-init.ts4
-rw-r--r--server/initializers/checker-before-init.ts4
-rw-r--r--server/initializers/config.ts8
-rw-r--r--server/initializers/constants.ts131
-rw-r--r--server/initializers/database.ts2
-rw-r--r--server/initializers/migrations/0005-email-pod.ts4
-rw-r--r--server/initializers/migrations/0010-email-user.ts4
-rw-r--r--server/initializers/migrations/0015-video-views.ts4
-rw-r--r--server/initializers/migrations/0020-video-likes.ts4
-rw-r--r--server/initializers/migrations/0025-video-dislikes.ts4
-rw-r--r--server/initializers/migrations/0030-video-category.ts4
-rw-r--r--server/initializers/migrations/0035-video-licence.ts4
-rw-r--r--server/initializers/migrations/0040-video-nsfw.ts4
-rw-r--r--server/initializers/migrations/0045-user-display-nsfw.ts4
-rw-r--r--server/initializers/migrations/0050-video-language.ts4
-rw-r--r--server/initializers/migrations/0055-video-uuid.ts4
-rw-r--r--server/initializers/migrations/0060-video-file.ts6
-rw-r--r--server/initializers/migrations/0065-video-file-size.ts6
-rw-r--r--server/initializers/migrations/0070-user-video-quota.ts6
-rw-r--r--server/initializers/migrations/0075-video-resolutions.ts6
-rw-r--r--server/initializers/migrations/0080-video-channels.ts6
-rw-r--r--server/initializers/migrations/0085-user-role.ts6
-rw-r--r--server/initializers/migrations/0090-videos-description.ts6
-rw-r--r--server/initializers/migrations/0095-videos-privacy.ts6
-rw-r--r--server/initializers/migrations/0100-activitypub.ts6
-rw-r--r--server/initializers/migrations/0105-server-mail.ts6
-rw-r--r--server/initializers/migrations/0110-server-key.ts6
-rw-r--r--server/initializers/migrations/0115-account-avatar.ts6
-rw-r--r--server/initializers/migrations/0120-video-null.ts6
-rw-r--r--server/initializers/migrations/0125-table-lowercase.ts4
-rw-r--r--server/initializers/migrations/0130-user-autoplay-video.ts4
-rw-r--r--server/initializers/migrations/0135-video-channel-actor.ts42
-rw-r--r--server/initializers/migrations/0140-actor-url.ts4
-rw-r--r--server/initializers/migrations/0145-delete-author.ts4
-rw-r--r--server/initializers/migrations/0150-avatar-cascade.ts4
-rw-r--r--server/initializers/migrations/0155-video-comments-enabled.ts4
-rw-r--r--server/initializers/migrations/0160-account-route.ts4
-rw-r--r--server/initializers/migrations/0165-video-route.ts4
-rw-r--r--server/initializers/migrations/0170-actor-follow-score.ts4
-rw-r--r--server/initializers/migrations/0175-actor-follow-counts.ts4
-rw-r--r--server/initializers/migrations/0180-job-table-delete.ts4
-rw-r--r--server/initializers/migrations/0185-video-share-url.ts4
-rw-r--r--server/initializers/migrations/0190-video-comment-unique-url.ts4
-rw-r--r--server/initializers/migrations/0195-support.ts4
-rw-r--r--server/initializers/migrations/0200-video-published-at.ts4
-rw-r--r--server/initializers/migrations/0205-user-nsfw-policy.ts4
-rw-r--r--server/initializers/migrations/0210-video-language.ts4
-rw-r--r--server/initializers/migrations/0215-video-support-length.ts4
-rw-r--r--server/initializers/migrations/0255-video-blacklist-reason.ts1
-rw-r--r--server/initializers/migrations/0285-description-support.ts6
-rw-r--r--server/initializers/migrations/0290-account-video-rate-url.ts6
-rw-r--r--server/initializers/migrations/0295-video-file-extname.ts6
-rw-r--r--server/initializers/migrations/0300-user-videos-history-enabled.ts6
-rw-r--r--server/initializers/migrations/0305-fix-unfederated-videos.ts6
-rw-r--r--server/initializers/migrations/0310-drop-unused-video-indexes.ts6
-rw-r--r--server/initializers/migrations/0315-user-notifications.ts4
-rw-r--r--server/initializers/migrations/0320-blacklist-unfederate.ts4
-rw-r--r--server/initializers/migrations/0325-video-abuse-fields.ts4
-rw-r--r--server/initializers/migrations/0330-video-streaming-playlist.ts4
-rw-r--r--server/initializers/migrations/0335-video-downloading-enabled.ts4
-rw-r--r--server/initializers/migrations/0340-add-originally-published-at.ts4
-rw-r--r--server/initializers/migrations/0345-video-playlists.ts4
-rw-r--r--server/initializers/migrations/0350-video-blacklist-type.ts6
-rw-r--r--server/initializers/migrations/0355-p2p-peer-version.ts6
-rw-r--r--server/initializers/migrations/0360-notification-instance-follower.ts6
-rw-r--r--server/initializers/migrations/0365-user-admin-flags.ts6
-rw-r--r--server/initializers/migrations/0370-thumbnail.ts6
-rw-r--r--server/initializers/migrations/0375-account-description.ts6
-rw-r--r--server/initializers/migrations/0380-cleanup-timestamps.ts6
-rw-r--r--server/initializers/migrations/0385-remove-actor-uuid.ts6
-rw-r--r--server/initializers/migrations/0390-user-pending-email.ts6
-rw-r--r--server/initializers/migrations/0395-user-video-languages.ts6
-rw-r--r--server/initializers/migrations/0400-user-theme.ts6
-rw-r--r--server/initializers/migrations/0405-plugin.ts6
-rw-r--r--server/initializers/migrations/0410-video-playlist-element.ts6
-rw-r--r--server/initializers/migrations/0415-thumbnail-auto-generated.ts6
-rw-r--r--server/initializers/migrations/0420-avatar-lazy.ts6
-rw-r--r--server/initializers/migrations/0425-nullable-actor-fields.ts6
-rw-r--r--server/initializers/migrations/0430-auto-follow-notification-setting.ts6
-rw-r--r--server/initializers/migrations/0435-user-modals.ts6
-rw-r--r--server/initializers/migrations/0440-user-auto-play-next-video.ts6
-rw-r--r--server/initializers/migrations/0445-shared-inbox-optional.ts6
-rw-r--r--server/initializers/migrations/0450-streaming-playlist-files.ts16
-rw-r--r--server/initializers/migrations/0455-soft-delete-video-comments.ts6
-rw-r--r--server/initializers/migrations/0460-user-playlist-autoplay.ts6
-rw-r--r--server/initializers/migrations/0465-thumbnail-file-url-length.ts6
-rw-r--r--server/initializers/migrations/0470-cleaup-indexes.ts6
-rw-r--r--server/initializers/migrations/0475-redundancy-expires-on.ts27
-rw-r--r--server/initializers/migrations/0480-caption-file-url.ts27
-rw-r--r--server/initializers/migrator.ts4
-rw-r--r--server/lib/activitypub/actor.ts22
-rw-r--r--server/lib/activitypub/audience.ts8
-rw-r--r--server/lib/activitypub/cache-file.ts4
-rw-r--r--server/lib/activitypub/crawl.ts6
-rw-r--r--server/lib/activitypub/follow.ts1
-rw-r--r--server/lib/activitypub/process/process-view.ts3
-rw-r--r--server/lib/activitypub/send/send-accept.ts2
-rw-r--r--server/lib/activitypub/send/send-announce.ts2
-rw-r--r--server/lib/activitypub/send/send-create.ts13
-rw-r--r--server/lib/activitypub/send/send-dislike.ts2
-rw-r--r--server/lib/activitypub/send/send-flag.ts2
-rw-r--r--server/lib/activitypub/send/send-like.ts2
-rw-r--r--server/lib/activitypub/send/send-reject.ts2
-rw-r--r--server/lib/activitypub/send/send-undo.ts10
-rw-r--r--server/lib/activitypub/send/send-update.ts5
-rw-r--r--server/lib/activitypub/send/send-view.ts6
-rw-r--r--server/lib/activitypub/send/utils.ts40
-rw-r--r--server/lib/activitypub/video-comments.ts14
-rw-r--r--server/lib/activitypub/video-rates.ts2
-rw-r--r--server/lib/activitypub/videos.ts159
-rw-r--r--server/lib/client-html.ts24
-rw-r--r--server/lib/emailer.ts101
-rw-r--r--server/lib/files-cache/videos-caption-cache.ts7
-rw-r--r--server/lib/files-cache/videos-preview-cache.ts13
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-broadcast.ts2
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-unicast.ts2
-rw-r--r--server/lib/job-queue/handlers/utils/activitypub-http-utils.ts8
-rw-r--r--server/lib/job-queue/handlers/video-file-import.ts2
-rw-r--r--server/lib/job-queue/handlers/video-import.ts2
-rw-r--r--server/lib/job-queue/handlers/video-redundancy.ts20
-rw-r--r--server/lib/job-queue/handlers/video-transcoding.ts17
-rw-r--r--server/lib/job-queue/handlers/video-views.ts4
-rw-r--r--server/lib/job-queue/job-queue.ts49
-rw-r--r--server/lib/moderation.ts22
-rw-r--r--server/lib/notifier.ts47
-rw-r--r--server/lib/plugins/plugin-index.ts4
-rw-r--r--server/lib/plugins/plugin-manager.ts44
-rw-r--r--server/lib/redis.ts41
-rw-r--r--server/lib/redundancy.ts8
-rw-r--r--server/lib/schedulers/auto-follow-index-instances.ts3
-rw-r--r--server/lib/schedulers/plugins-check-scheduler.ts2
-rw-r--r--server/lib/schedulers/remove-old-views-scheduler.ts2
-rw-r--r--server/lib/schedulers/update-videos-scheduler.ts1
-rw-r--r--server/lib/schedulers/videos-redundancy-scheduler.ts63
-rw-r--r--server/lib/thumbnail.ts18
-rw-r--r--server/lib/user.ts16
-rw-r--r--server/lib/video-blacklist.ts20
-rw-r--r--server/lib/video-channel.ts3
-rw-r--r--server/lib/video-comment.ts6
-rw-r--r--server/middlewares/activitypub.ts14
-rw-r--r--server/middlewares/csp.ts30
-rw-r--r--server/middlewares/dnt.ts3
-rw-r--r--server/middlewares/oauth.ts1
-rw-r--r--server/middlewares/sort.ts25
-rw-r--r--server/middlewares/validators/avatar.ts4
-rw-r--r--server/middlewares/validators/config.ts2
-rw-r--r--server/middlewares/validators/feeds.ts8
-rw-r--r--server/middlewares/validators/redundancy.ts74
-rw-r--r--server/middlewares/validators/sort.ts3
-rw-r--r--server/middlewares/validators/users.ts7
-rw-r--r--server/middlewares/validators/videos/video-captions.ts10
-rw-r--r--server/middlewares/validators/videos/video-comments.ts2
-rw-r--r--server/middlewares/validators/videos/video-imports.ts9
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts9
-rw-r--r--server/middlewares/validators/videos/video-rates.ts6
-rw-r--r--server/middlewares/validators/videos/videos.ts36
-rw-r--r--server/middlewares/validators/webfinger.ts5
-rw-r--r--server/models/account/account-blocklist.ts2
-rw-r--r--server/models/account/account-video-rate.ts12
-rw-r--r--server/models/account/account.ts69
-rw-r--r--server/models/account/user-notification.ts4
-rw-r--r--server/models/account/user.ts86
-rw-r--r--server/models/activitypub/actor-follow.ts53
-rw-r--r--server/models/activitypub/actor.ts101
-rw-r--r--server/models/model-cache.ts91
-rw-r--r--server/models/oauth/oauth-token.ts4
-rw-r--r--server/models/redundancy/video-redundancy.ts218
-rw-r--r--server/models/server/plugin.ts8
-rw-r--r--server/models/server/server-blocklist.ts2
-rw-r--r--server/models/utils.ts4
-rw-r--r--server/models/video/thumbnail.ts15
-rw-r--r--server/models/video/video-abuse.ts6
-rw-r--r--server/models/video/video-caption.ts27
-rw-r--r--server/models/video/video-channel.ts33
-rw-r--r--server/models/video/video-comment.ts20
-rw-r--r--server/models/video/video-format-utils.ts34
-rw-r--r--server/models/video/video-playlist-element.ts8
-rw-r--r--server/models/video/video-playlist.ts36
-rw-r--r--server/models/video/video.ts368
-rw-r--r--server/tests/api/activitypub/client.ts4
-rw-r--r--server/tests/api/activitypub/fetch.ts4
-rw-r--r--server/tests/api/activitypub/helpers.ts2
-rw-r--r--server/tests/api/activitypub/refresher.ts70
-rw-r--r--server/tests/api/activitypub/security.ts13
-rw-r--r--server/tests/api/check-params/accounts.ts2
-rw-r--r--server/tests/api/check-params/blocklist.ts2
-rw-r--r--server/tests/api/check-params/config.ts2
-rw-r--r--server/tests/api/check-params/contact-form.ts18
-rw-r--r--server/tests/api/check-params/debug.ts7
-rw-r--r--server/tests/api/check-params/follows.ts2
-rw-r--r--server/tests/api/check-params/jobs.ts8
-rw-r--r--server/tests/api/check-params/logs.ts8
-rw-r--r--server/tests/api/check-params/plugins.ts2
-rw-r--r--server/tests/api/check-params/redundancy.ts145
-rw-r--r--server/tests/api/check-params/search.ts2
-rw-r--r--server/tests/api/check-params/services.ts2
-rw-r--r--server/tests/api/check-params/user-notifications.ts2
-rw-r--r--server/tests/api/check-params/user-subscriptions.ts2
-rw-r--r--server/tests/api/check-params/users.ts13
-rw-r--r--server/tests/api/check-params/video-abuses.ts4
-rw-r--r--server/tests/api/check-params/video-blacklist.ts15
-rw-r--r--server/tests/api/check-params/video-captions.ts4
-rw-r--r--server/tests/api/check-params/video-channels.ts12
-rw-r--r--server/tests/api/check-params/video-comments.ts2
-rw-r--r--server/tests/api/check-params/video-imports.ts17
-rw-r--r--server/tests/api/check-params/video-playlists.ts7
-rw-r--r--server/tests/api/check-params/videos-filter.ts6
-rw-r--r--server/tests/api/check-params/videos-history.ts9
-rw-r--r--server/tests/api/check-params/videos.ts38
-rw-r--r--server/tests/api/notifications/user-notifications.ts88
-rw-r--r--server/tests/api/redundancy/index.ts1
-rw-r--r--server/tests/api/redundancy/manage-redundancy.ts373
-rw-r--r--server/tests/api/redundancy/redundancy.ts182
-rw-r--r--server/tests/api/search/search-activitypub-video-channels.ts44
-rw-r--r--server/tests/api/search/search-activitypub-videos.ts8
-rw-r--r--server/tests/api/search/search-videos.ts14
-rw-r--r--server/tests/api/server/auto-follows.ts19
-rw-r--r--server/tests/api/server/config.ts11
-rw-r--r--server/tests/api/server/contact-form.ts12
-rw-r--r--server/tests/api/server/email.ts2
-rw-r--r--server/tests/api/server/follow-constraints.ts8
-rw-r--r--server/tests/api/server/follows-moderation.ts18
-rw-r--r--server/tests/api/server/follows.ts88
-rw-r--r--server/tests/api/server/handle-down.ts36
-rw-r--r--server/tests/api/server/jobs.ts16
-rw-r--r--server/tests/api/server/logs.ts2
-rw-r--r--server/tests/api/server/no-client.ts2
-rw-r--r--server/tests/api/server/plugins.ts25
-rw-r--r--server/tests/api/server/reverse-proxy.ts2
-rw-r--r--server/tests/api/server/stats.ts7
-rw-r--r--server/tests/api/server/tracker.ts2
-rw-r--r--server/tests/api/users/blocklist.ts259
-rw-r--r--server/tests/api/users/user-subscriptions.ts15
-rw-r--r--server/tests/api/users/users-multiple-servers.ts11
-rw-r--r--server/tests/api/users/users-verification.ts2
-rw-r--r--server/tests/api/users/users.ts55
-rw-r--r--server/tests/api/videos/audio-only.ts27
-rw-r--r--server/tests/api/videos/multiple-servers.ts32
-rw-r--r--server/tests/api/videos/services.ts10
-rw-r--r--server/tests/api/videos/single-server.ts2
-rw-r--r--server/tests/api/videos/video-abuse.ts18
-rw-r--r--server/tests/api/videos/video-blacklist.ts86
-rw-r--r--server/tests/api/videos/video-captions.ts9
-rw-r--r--server/tests/api/videos/video-change-ownership.ts18
-rw-r--r--server/tests/api/videos/video-channels.ts35
-rw-r--r--server/tests/api/videos/video-comments.ts6
-rw-r--r--server/tests/api/videos/video-description.ts7
-rw-r--r--server/tests/api/videos/video-hls.ts29
-rw-r--r--server/tests/api/videos/video-imports.ts11
-rw-r--r--server/tests/api/videos/video-nsfw.ts34
-rw-r--r--server/tests/api/videos/video-playlist-thumbnails.ts53
-rw-r--r--server/tests/api/videos/video-playlists.ts392
-rw-r--r--server/tests/api/videos/video-privacy.ts11
-rw-r--r--server/tests/api/videos/video-schedule-update.ts3
-rw-r--r--server/tests/api/videos/video-transcoder.ts84
-rw-r--r--server/tests/api/videos/videos-filter.ts12
-rw-r--r--server/tests/api/videos/videos-history.ts2
-rw-r--r--server/tests/api/videos/videos-overview.ts2
-rw-r--r--server/tests/api/videos/videos-views-cleaner.ts16
-rw-r--r--server/tests/cli/create-import-video-file-job.ts6
-rw-r--r--server/tests/cli/create-transcoding-job.ts15
-rw-r--r--server/tests/cli/optimize-old-videos.ts4
-rw-r--r--server/tests/cli/peertube.ts84
-rw-r--r--server/tests/cli/plugins.ts2
-rw-r--r--server/tests/cli/prune-storage.ts19
-rw-r--r--server/tests/cli/update-host.ts2
-rw-r--r--server/tests/client.ts2
-rw-r--r--server/tests/feeds/feeds.ts58
-rw-r--r--server/tests/helpers/comment-model.ts4
-rw-r--r--server/tests/helpers/core-utils.ts2
-rw-r--r--server/tests/helpers/request.ts2
-rw-r--r--server/tests/misc-endpoints.ts2
-rw-r--r--server/tests/plugins/action-hooks.ts17
-rw-r--r--server/tests/plugins/filter-hooks.ts41
-rw-r--r--server/tests/plugins/translations.ts35
-rw-r--r--server/tests/plugins/video-constants.ts58
-rw-r--r--server/tests/real-world/populate-database.ts122
-rw-r--r--server/tests/real-world/real-world.ts375
-rw-r--r--server/tools/cli.ts62
-rw-r--r--server/tools/package.json5
-rw-r--r--server/tools/peertube-auth.ts15
-rw-r--r--server/tools/peertube-import-videos.ts87
-rw-r--r--server/tools/peertube-plugins.ts34
-rw-r--r--server/tools/peertube-redundancy.ts197
-rw-r--r--server/tools/peertube-repl.ts41
-rw-r--r--server/tools/peertube-upload.ts21
-rw-r--r--server/tools/peertube-watch.ts10
-rw-r--r--server/tools/peertube.ts24
-rw-r--r--server/tools/yarn.lock23
-rw-r--r--server/typings/express.ts4
-rw-r--r--server/typings/models/account/account-blocklist.ts6
-rw-r--r--server/typings/models/account/account.ts42
-rw-r--r--server/typings/models/account/actor-follow.ts24
-rw-r--r--server/typings/models/account/actor.ts54
-rw-r--r--server/typings/models/account/avatar.ts3
-rw-r--r--server/typings/models/oauth/oauth-token.ts3
-rw-r--r--server/typings/models/server/plugin.ts3
-rw-r--r--server/typings/models/server/server-blocklist.ts6
-rw-r--r--server/typings/models/server/server.ts6
-rw-r--r--server/typings/models/user/user-notification.ts42
-rw-r--r--server/typings/models/user/user.ts33
-rw-r--r--server/typings/models/video/schedule-video-update.ts3
-rw-r--r--server/typings/models/video/video-abuse.ts9
-rw-r--r--server/typings/models/video/video-blacklist.ts9
-rw-r--r--server/typings/models/video/video-caption.ts7
-rw-r--r--server/typings/models/video/video-change-ownership.ts6
-rw-r--r--server/typings/models/video/video-channels.ts57
-rw-r--r--server/typings/models/video/video-comment.ts27
-rw-r--r--server/typings/models/video/video-file.ts21
-rw-r--r--server/typings/models/video/video-import.ts12
-rw-r--r--server/typings/models/video/video-playlist-element.ts12
-rw-r--r--server/typings/models/video/video-playlist.ts36
-rw-r--r--server/typings/models/video/video-rate.ts9
-rw-r--r--server/typings/models/video/video-redundancy.ts15
-rw-r--r--server/typings/models/video/video-share.ts6
-rw-r--r--server/typings/models/video/video-streaming-playlist.ts22
-rw-r--r--server/typings/models/video/video.ts94
-rw-r--r--server/typings/utils.ts2
370 files changed, 4931 insertions, 3612 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index 62412cd62..84828e7e0 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -1,4 +1,3 @@
1// Intercept ActivityPub client requests
2import * as express from 'express' 1import * as express from 'express'
3import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' 2import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos'
4import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' 3import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub'
@@ -14,7 +13,7 @@ import {
14 videosCustomGetValidator, 13 videosCustomGetValidator,
15 videosShareValidator 14 videosShareValidator
16} from '../../middlewares' 15} from '../../middlewares'
17import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' 16import { getAccountVideoRateValidatorFactory, videoCommentGetValidator } from '../../middlewares/validators'
18import { AccountModel } from '../../models/account/account' 17import { AccountModel } from '../../models/account/account'
19import { ActorFollowModel } from '../../models/activitypub/actor-follow' 18import { ActorFollowModel } from '../../models/activitypub/actor-follow'
20import { VideoModel } from '../../models/video/video' 19import { VideoModel } from '../../models/video/video'
@@ -37,10 +36,12 @@ import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike'
37import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' 36import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists'
38import { VideoPlaylistModel } from '../../models/video/video-playlist' 37import { VideoPlaylistModel } from '../../models/video/video-playlist'
39import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' 38import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
40import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption } from '@server/typings/models' 39import { MAccountId, MActorId, MVideoAPWithoutCaption, MVideoId } from '@server/typings/models'
41 40
42const activityPubClientRouter = express.Router() 41const activityPubClientRouter = express.Router()
43 42
43// Intercept ActivityPub client requests
44
44activityPubClientRouter.get('/accounts?/:name', 45activityPubClientRouter.get('/accounts?/:name',
45 executeIfActivityPub, 46 executeIfActivityPub,
46 asyncMiddleware(localAccountValidator), 47 asyncMiddleware(localAccountValidator),
@@ -63,13 +64,13 @@ activityPubClientRouter.get('/accounts?/:name/playlists',
63) 64)
64activityPubClientRouter.get('/accounts?/:name/likes/:videoId', 65activityPubClientRouter.get('/accounts?/:name/likes/:videoId',
65 executeIfActivityPub, 66 executeIfActivityPub,
66 asyncMiddleware(getAccountVideoRateValidator('like')), 67 asyncMiddleware(getAccountVideoRateValidatorFactory('like')),
67 getAccountVideoRate('like') 68 getAccountVideoRateFactory('like')
68) 69)
69activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', 70activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId',
70 executeIfActivityPub, 71 executeIfActivityPub,
71 asyncMiddleware(getAccountVideoRateValidator('dislike')), 72 asyncMiddleware(getAccountVideoRateValidatorFactory('dislike')),
72 getAccountVideoRate('dislike') 73 getAccountVideoRateFactory('dislike')
73) 74)
74 75
75activityPubClientRouter.get('/videos/watch/:id', 76activityPubClientRouter.get('/videos/watch/:id',
@@ -85,7 +86,7 @@ activityPubClientRouter.get('/videos/watch/:id/activity',
85) 86)
86activityPubClientRouter.get('/videos/watch/:id/announces', 87activityPubClientRouter.get('/videos/watch/:id/announces',
87 executeIfActivityPub, 88 executeIfActivityPub,
88 asyncMiddleware(videosCustomGetValidator('only-video')), 89 asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
89 asyncMiddleware(videoAnnouncesController) 90 asyncMiddleware(videoAnnouncesController)
90) 91)
91activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', 92activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
@@ -95,17 +96,17 @@ activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
95) 96)
96activityPubClientRouter.get('/videos/watch/:id/likes', 97activityPubClientRouter.get('/videos/watch/:id/likes',
97 executeIfActivityPub, 98 executeIfActivityPub,
98 asyncMiddleware(videosCustomGetValidator('only-video')), 99 asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
99 asyncMiddleware(videoLikesController) 100 asyncMiddleware(videoLikesController)
100) 101)
101activityPubClientRouter.get('/videos/watch/:id/dislikes', 102activityPubClientRouter.get('/videos/watch/:id/dislikes',
102 executeIfActivityPub, 103 executeIfActivityPub,
103 asyncMiddleware(videosCustomGetValidator('only-video')), 104 asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
104 asyncMiddleware(videoDislikesController) 105 asyncMiddleware(videoDislikesController)
105) 106)
106activityPubClientRouter.get('/videos/watch/:id/comments', 107activityPubClientRouter.get('/videos/watch/:id/comments',
107 executeIfActivityPub, 108 executeIfActivityPub,
108 asyncMiddleware(videosCustomGetValidator('only-video')), 109 asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
109 asyncMiddleware(videoCommentsController) 110 asyncMiddleware(videoCommentsController)
110) 111)
111activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', 112activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId',
@@ -122,7 +123,7 @@ activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity
122activityPubClientRouter.get('/video-channels/:name', 123activityPubClientRouter.get('/video-channels/:name',
123 executeIfActivityPub, 124 executeIfActivityPub,
124 asyncMiddleware(localVideoChannelValidator), 125 asyncMiddleware(localVideoChannelValidator),
125 asyncMiddleware(videoChannelController) 126 videoChannelController
126) 127)
127activityPubClientRouter.get('/video-channels/:name/followers', 128activityPubClientRouter.get('/video-channels/:name/followers',
128 executeIfActivityPub, 129 executeIfActivityPub,
@@ -154,7 +155,7 @@ activityPubClientRouter.get('/video-playlists/:playlistId',
154activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', 155activityPubClientRouter.get('/video-playlists/:playlistId/:videoId',
155 executeIfActivityPub, 156 executeIfActivityPub,
156 asyncMiddleware(videoPlaylistElementAPGetValidator), 157 asyncMiddleware(videoPlaylistElementAPGetValidator),
157 asyncMiddleware(videoPlaylistElementController) 158 videoPlaylistElementController
158) 159)
159 160
160// --------------------------------------------------------------------------- 161// ---------------------------------------------------------------------------
@@ -192,7 +193,7 @@ async function accountPlaylistsController (req: express.Request, res: express.Re
192 return activityPubResponse(activityPubContextify(activityPubResult), res) 193 return activityPubResponse(activityPubContextify(activityPubResult), res)
193} 194}
194 195
195function getAccountVideoRate (rateType: VideoRateType) { 196function getAccountVideoRateFactory (rateType: VideoRateType) {
196 return (req: express.Request, res: express.Response) => { 197 return (req: express.Request, res: express.Response) => {
197 const accountVideoRate = res.locals.accountVideoRate 198 const accountVideoRate = res.locals.accountVideoRate
198 199
@@ -234,11 +235,11 @@ async function videoAnnounceController (req: express.Request, res: express.Respo
234 235
235 const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined) 236 const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined)
236 237
237 return activityPubResponse(activityPubContextify(activity), res) 238 return activityPubResponse(activityPubContextify(activity, 'Announce'), res)
238} 239}
239 240
240async function videoAnnouncesController (req: express.Request, res: express.Response) { 241async function videoAnnouncesController (req: express.Request, res: express.Response) {
241 const video = res.locals.onlyVideo 242 const video = res.locals.onlyImmutableVideo
242 243
243 const handler = async (start: number, count: number) => { 244 const handler = async (start: number, count: number) => {
244 const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) 245 const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count)
@@ -253,21 +254,21 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp
253} 254}
254 255
255async function videoLikesController (req: express.Request, res: express.Response) { 256async function videoLikesController (req: express.Request, res: express.Response) {
256 const video = res.locals.onlyVideo 257 const video = res.locals.onlyImmutableVideo
257 const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) 258 const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video))
258 259
259 return activityPubResponse(activityPubContextify(json), res) 260 return activityPubResponse(activityPubContextify(json), res)
260} 261}
261 262
262async function videoDislikesController (req: express.Request, res: express.Response) { 263async function videoDislikesController (req: express.Request, res: express.Response) {
263 const video = res.locals.onlyVideo 264 const video = res.locals.onlyImmutableVideo
264 const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) 265 const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video))
265 266
266 return activityPubResponse(activityPubContextify(json), res) 267 return activityPubResponse(activityPubContextify(json), res)
267} 268}
268 269
269async function videoCommentsController (req: express.Request, res: express.Response) { 270async function videoCommentsController (req: express.Request, res: express.Response) {
270 const video = res.locals.onlyVideo 271 const video = res.locals.onlyImmutableVideo
271 272
272 const handler = async (start: number, count: number) => { 273 const handler = async (start: number, count: number) => {
273 const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) 274 const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count)
@@ -281,7 +282,7 @@ async function videoCommentsController (req: express.Request, res: express.Respo
281 return activityPubResponse(activityPubContextify(json), res) 282 return activityPubResponse(activityPubContextify(json), res)
282} 283}
283 284
284async function videoChannelController (req: express.Request, res: express.Response) { 285function videoChannelController (req: express.Request, res: express.Response) {
285 const videoChannel = res.locals.videoChannel 286 const videoChannel = res.locals.videoChannel
286 287
287 return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject()), res) 288 return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject()), res)
@@ -334,10 +335,10 @@ async function videoRedundancyController (req: express.Request, res: express.Res
334 335
335 if (req.path.endsWith('/activity')) { 336 if (req.path.endsWith('/activity')) {
336 const data = buildCreateActivity(videoRedundancy.url, serverActor, object, audience) 337 const data = buildCreateActivity(videoRedundancy.url, serverActor, object, audience)
337 return activityPubResponse(activityPubContextify(data), res) 338 return activityPubResponse(activityPubContextify(data, 'CacheFile'), res)
338 } 339 }
339 340
340 return activityPubResponse(activityPubContextify(object), res) 341 return activityPubResponse(activityPubContextify(object, 'CacheFile'), res)
341} 342}
342 343
343async function videoPlaylistController (req: express.Request, res: express.Response) { 344async function videoPlaylistController (req: express.Request, res: express.Response) {
@@ -353,7 +354,7 @@ async function videoPlaylistController (req: express.Request, res: express.Respo
353 return activityPubResponse(activityPubContextify(object), res) 354 return activityPubResponse(activityPubContextify(object), res)
354} 355}
355 356
356async function videoPlaylistElementController (req: express.Request, res: express.Response) { 357function videoPlaylistElementController (req: express.Request, res: express.Response) {
357 const videoPlaylistElement = res.locals.videoPlaylistElementAP 358 const videoPlaylistElement = res.locals.videoPlaylistElementAP
358 359
359 const json = videoPlaylistElement.toActivityPubObject() 360 const json = videoPlaylistElement.toActivityPubObject()
@@ -386,7 +387,7 @@ async function actorPlaylists (req: express.Request, account: MAccountId) {
386 return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) 387 return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
387} 388}
388 389
389function videoRates (req: express.Request, rateType: VideoRateType, video: MVideo, url: string) { 390function videoRates (req: express.Request, rateType: VideoRateType, video: MVideoId, url: string) {
390 const handler = async (start: number, count: number) => { 391 const handler = async (start: number, count: number) => {
391 const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) 392 const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count)
392 return { 393 return {
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts
index ca42106b8..3b8fb34a8 100644
--- a/server/controllers/activitypub/inbox.ts
+++ b/server/controllers/activitypub/inbox.ts
@@ -46,11 +46,15 @@ const inboxQueue = queue<QueueParam, Error>((task, cb) => {
46 46
47 processActivities(task.activities, options) 47 processActivities(task.activities, options)
48 .then(() => cb()) 48 .then(() => cb())
49 .catch(err => {
50 logger.error('Error in process activities.', { err })
51 cb()
52 })
49}) 53})
50 54
51function inboxController (req: express.Request, res: express.Response) { 55function inboxController (req: express.Request, res: express.Response) {
52 const rootActivity: RootActivity = req.body 56 const rootActivity: RootActivity = req.body
53 let activities: Activity[] = [] 57 let activities: Activity[]
54 58
55 if ([ 'Collection', 'CollectionPage' ].indexOf(rootActivity.type) !== -1) { 59 if ([ 'Collection', 'CollectionPage' ].indexOf(rootActivity.type) !== -1) {
56 activities = (rootActivity as ActivityPubCollection).items 60 activities = (rootActivity as ActivityPubCollection).items
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index 05740318e..f354ccf24 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -16,21 +16,17 @@ import {
16 accountNameWithHostGetValidator, 16 accountNameWithHostGetValidator,
17 accountsSortValidator, 17 accountsSortValidator,
18 ensureAuthUserOwnsAccountValidator, 18 ensureAuthUserOwnsAccountValidator,
19 videosSortValidator, 19 videoChannelsSortValidator,
20 videoChannelsSortValidator 20 videosSortValidator
21} from '../../middlewares/validators' 21} from '../../middlewares/validators'
22import { AccountModel } from '../../models/account/account' 22import { AccountModel } from '../../models/account/account'
23import { AccountVideoRateModel } from '../../models/account/account-video-rate' 23import { AccountVideoRateModel } from '../../models/account/account-video-rate'
24import { VideoModel } from '../../models/video/video' 24import { VideoModel } from '../../models/video/video'
25import { buildNSFWFilter, isUserAbleToSearchRemoteURI, getCountVideos } from '../../helpers/express-utils' 25import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
26import { VideoChannelModel } from '../../models/video/video-channel' 26import { VideoChannelModel } from '../../models/video/video-channel'
27import { JobQueue } from '../../lib/job-queue' 27import { JobQueue } from '../../lib/job-queue'
28import { logger } from '../../helpers/logger'
29import { VideoPlaylistModel } from '../../models/video/video-playlist' 28import { VideoPlaylistModel } from '../../models/video/video-playlist'
30import { 29import { commonVideoPlaylistFiltersValidator, videoPlaylistsSearchValidator } from '../../middlewares/validators/videos/video-playlists'
31 commonVideoPlaylistFiltersValidator,
32 videoPlaylistsSearchValidator
33} from '../../middlewares/validators/videos/video-playlists'
34 30
35const accountsRouter = express.Router() 31const accountsRouter = express.Router()
36 32
@@ -104,7 +100,6 @@ function getAccount (req: express.Request, res: express.Response) {
104 100
105 if (account.isOutdated()) { 101 if (account.isOutdated()) {
106 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } }) 102 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } })
107 .catch(err => logger.error('Cannot create AP refresher job for actor %s.', account.Actor.url, { err }))
108 } 103 }
109 104
110 return res.json(account.toFormattedJSON()) 105 return res.json(account.toFormattedJSON())
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index ae4e00248..69940f395 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -31,12 +31,12 @@ configRouter.get('/',
31configRouter.get('/custom', 31configRouter.get('/custom',
32 authenticate, 32 authenticate,
33 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), 33 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
34 asyncMiddleware(getCustomConfig) 34 getCustomConfig
35) 35)
36configRouter.put('/custom', 36configRouter.put('/custom',
37 authenticate, 37 authenticate,
38 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), 38 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
39 asyncMiddleware(customConfigUpdateValidator), 39 customConfigUpdateValidator,
40 asyncMiddleware(updateCustomConfig) 40 asyncMiddleware(updateCustomConfig)
41) 41)
42configRouter.delete('/custom', 42configRouter.delete('/custom',
@@ -196,7 +196,7 @@ function getAbout (req: express.Request, res: express.Response) {
196 return res.json(about).end() 196 return res.json(about).end()
197} 197}
198 198
199async function getCustomConfig (req: express.Request, res: express.Response) { 199function getCustomConfig (req: express.Request, res: express.Response) {
200 const data = customConfig() 200 const data = customConfig()
201 201
202 return res.json(data).end() 202 return res.json(data).end()
@@ -250,7 +250,7 @@ function getRegisteredThemes () {
250 250
251function getEnabledResolutions () { 251function getEnabledResolutions () {
252 return Object.keys(CONFIG.TRANSCODING.RESOLUTIONS) 252 return Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
253 .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[ key ] === true) 253 .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
254 .map(r => parseInt(r, 10)) 254 .map(r => parseInt(r, 10))
255} 255}
256 256
@@ -340,13 +340,13 @@ function customConfig (): CustomConfig {
340 allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES, 340 allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
341 threads: CONFIG.TRANSCODING.THREADS, 341 threads: CONFIG.TRANSCODING.THREADS,
342 resolutions: { 342 resolutions: {
343 '0p': CONFIG.TRANSCODING.RESOLUTIONS[ '0p' ], 343 '0p': CONFIG.TRANSCODING.RESOLUTIONS['0p'],
344 '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ], 344 '240p': CONFIG.TRANSCODING.RESOLUTIONS['240p'],
345 '360p': CONFIG.TRANSCODING.RESOLUTIONS[ '360p' ], 345 '360p': CONFIG.TRANSCODING.RESOLUTIONS['360p'],
346 '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ], 346 '480p': CONFIG.TRANSCODING.RESOLUTIONS['480p'],
347 '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ], 347 '720p': CONFIG.TRANSCODING.RESOLUTIONS['720p'],
348 '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ], 348 '1080p': CONFIG.TRANSCODING.RESOLUTIONS['1080p'],
349 '2160p': CONFIG.TRANSCODING.RESOLUTIONS[ '2160p' ] 349 '2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p']
350 }, 350 },
351 webtorrent: { 351 webtorrent: {
352 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED 352 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts
index 6138a32de..7bec6c527 100644
--- a/server/controllers/api/index.ts
+++ b/server/controllers/api/index.ts
@@ -1,5 +1,4 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as RateLimit from 'express-rate-limit'
3import { configRouter } from './config' 2import { configRouter } from './config'
4import { jobsRouter } from './jobs' 3import { jobsRouter } from './jobs'
5import { oauthClientsRouter } from './oauth-clients' 4import { oauthClientsRouter } from './oauth-clients'
@@ -15,6 +14,7 @@ import { overviewsRouter } from './overviews'
15import { videoPlaylistRouter } from './video-playlist' 14import { videoPlaylistRouter } from './video-playlist'
16import { CONFIG } from '../../initializers/config' 15import { CONFIG } from '../../initializers/config'
17import { pluginRouter } from './plugins' 16import { pluginRouter } from './plugins'
17import * as RateLimit from 'express-rate-limit'
18 18
19const apiRouter = express.Router() 19const apiRouter = express.Router()
20 20
@@ -24,8 +24,6 @@ apiRouter.use(cors({
24 credentials: true 24 credentials: true
25})) 25}))
26 26
27// FIXME: https://github.com/nfriedly/express-rate-limit/issues/138
28// @ts-ignore
29const apiRateLimiter = RateLimit({ 27const apiRateLimiter = RateLimit({
30 windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS, 28 windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS,
31 max: CONFIG.RATES_LIMIT.API.MAX 29 max: CONFIG.RATES_LIMIT.API.MAX
diff --git a/server/controllers/api/jobs.ts b/server/controllers/api/jobs.ts
index 05320311e..13fc04d18 100644
--- a/server/controllers/api/jobs.ts
+++ b/server/controllers/api/jobs.ts
@@ -50,7 +50,7 @@ async function listJobs (req: express.Request, res: express.Response) {
50 }) 50 })
51 const total = await JobQueue.Instance.count(state) 51 const total = await JobQueue.Instance.count(state)
52 52
53 const result: ResultList<any> = { 53 const result: ResultList<Job> = {
54 total, 54 total,
55 data: jobs.map(j => formatJob(j, state)) 55 data: jobs.map(j => formatJob(j, state))
56 } 56 }
diff --git a/server/controllers/api/overviews.ts b/server/controllers/api/overviews.ts
index 46e76ac6b..75f3baedb 100644
--- a/server/controllers/api/overviews.ts
+++ b/server/controllers/api/overviews.ts
@@ -24,7 +24,7 @@ export { overviewsRouter }
24const buildSamples = memoizee(async function () { 24const buildSamples = memoizee(async function () {
25 const [ categories, channels, tags ] = await Promise.all([ 25 const [ categories, channels, tags ] = await Promise.all([
26 VideoModel.getRandomFieldSamples('category', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT), 26 VideoModel.getRandomFieldSamples('category', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT),
27 VideoModel.getRandomFieldSamples('channelId', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD ,OVERVIEWS.VIDEOS.SAMPLES_COUNT), 27 VideoModel.getRandomFieldSamples('channelId', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT),
28 TagModel.getRandomSamples(OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT) 28 TagModel.getRandomSamples(OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT)
29 ]) 29 ])
30 30
diff --git a/server/controllers/api/server/debug.ts b/server/controllers/api/server/debug.ts
index 4450038f6..e12fc1dd4 100644
--- a/server/controllers/api/server/debug.ts
+++ b/server/controllers/api/server/debug.ts
@@ -1,13 +1,13 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users' 2import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' 3import { authenticate, ensureUserHasRight } from '../../../middlewares'
4 4
5const debugRouter = express.Router() 5const debugRouter = express.Router()
6 6
7debugRouter.get('/debug', 7debugRouter.get('/debug',
8 authenticate, 8 authenticate,
9 ensureUserHasRight(UserRight.MANAGE_DEBUG), 9 ensureUserHasRight(UserRight.MANAGE_DEBUG),
10 asyncMiddleware(getDebug) 10 getDebug
11) 11)
12 12
13// --------------------------------------------------------------------------- 13// ---------------------------------------------------------------------------
@@ -18,7 +18,7 @@ export {
18 18
19// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
20 20
21async function getDebug (req: express.Request, res: express.Response) { 21function getDebug (req: express.Request, res: express.Response) {
22 return res.json({ 22 return res.json({
23 ip: req.ip 23 ip: req.ip
24 }).end() 24 }).end()
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts
index 29a403a43..0bc20bd60 100644
--- a/server/controllers/api/server/follows.ts
+++ b/server/controllers/api/server/follows.ts
@@ -24,7 +24,7 @@ import {
24} from '../../../middlewares/validators' 24} from '../../../middlewares/validators'
25import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 25import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
26import { JobQueue } from '../../../lib/job-queue' 26import { JobQueue } from '../../../lib/job-queue'
27import { removeRedundancyOf } from '../../../lib/redundancy' 27import { removeRedundanciesOfServer } from '../../../lib/redundancy'
28import { sequelizeTypescript } from '../../../initializers/database' 28import { sequelizeTypescript } from '../../../initializers/database'
29import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow' 29import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow'
30 30
@@ -135,7 +135,6 @@ async function followInstance (req: express.Request, res: express.Response) {
135 } 135 }
136 136
137 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) 137 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
138 .catch(err => logger.error('Cannot create follow job for %s.', host, err))
139 } 138 }
140 139
141 return res.status(204).end() 140 return res.status(204).end()
@@ -153,7 +152,7 @@ async function removeFollowing (req: express.Request, res: express.Response) {
153 await server.save({ transaction: t }) 152 await server.save({ transaction: t })
154 153
155 // Async, could be long 154 // Async, could be long
156 removeRedundancyOf(server.id) 155 removeRedundanciesOfServer(server.id)
157 .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, err)) 156 .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, err))
158 157
159 await follow.destroy({ transaction: t }) 158 await follow.destroy({ transaction: t })
diff --git a/server/controllers/api/server/logs.ts b/server/controllers/api/server/logs.ts
index cd1e0f5bf..4b543d686 100644
--- a/server/controllers/api/server/logs.ts
+++ b/server/controllers/api/server/logs.ts
@@ -59,9 +59,9 @@ async function getLogs (req: express.Request, res: express.Response) {
59} 59}
60 60
61async function generateOutput (options: { 61async function generateOutput (options: {
62 startDateQuery: string, 62 startDateQuery: string
63 endDateQuery?: string, 63 endDateQuery?: string
64 level: LogLevel, 64 level: LogLevel
65 nameFilter: RegExp 65 nameFilter: RegExp
66}) { 66}) {
67 const { startDateQuery, level, nameFilter } = options 67 const { startDateQuery, level, nameFilter } = options
@@ -111,7 +111,7 @@ async function getOutputFromFile (path: string, startDate: Date, endDate: Date,
111 const output: any[] = [] 111 const output: any[] = []
112 112
113 for (let i = lines.length - 1; i >= 0; i--) { 113 for (let i = lines.length - 1; i >= 0; i--) {
114 const line = lines[ i ] 114 const line = lines[i]
115 let log: any 115 let log: any
116 116
117 try { 117 try {
@@ -122,7 +122,7 @@ async function getOutputFromFile (path: string, startDate: Date, endDate: Date,
122 } 122 }
123 123
124 logTime = new Date(log.timestamp).getTime() 124 logTime = new Date(log.timestamp).getTime()
125 if (logTime >= startTime && logTime <= endTime && logsLevel[ log.level ] >= logsLevel[ level ]) { 125 if (logTime >= startTime && logTime <= endTime && logsLevel[log.level] >= logsLevel[level]) {
126 output.push(log) 126 output.push(log)
127 127
128 currentSize += line.length 128 currentSize += line.length
diff --git a/server/controllers/api/server/redundancy.ts b/server/controllers/api/server/redundancy.ts
index 4ea6164a3..1ced0759e 100644
--- a/server/controllers/api/server/redundancy.ts
+++ b/server/controllers/api/server/redundancy.ts
@@ -1,9 +1,24 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users' 2import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' 3import {
4import { updateServerRedundancyValidator } from '../../../middlewares/validators/redundancy' 4 asyncMiddleware,
5import { removeRedundancyOf } from '../../../lib/redundancy' 5 authenticate,
6 ensureUserHasRight,
7 paginationValidator,
8 setDefaultPagination,
9 setDefaultVideoRedundanciesSort,
10 videoRedundanciesSortValidator
11} from '../../../middlewares'
12import {
13 listVideoRedundanciesValidator,
14 updateServerRedundancyValidator,
15 addVideoRedundancyValidator,
16 removeVideoRedundancyValidator
17} from '../../../middlewares/validators/redundancy'
18import { removeRedundanciesOfServer, removeVideoRedundancy } from '../../../lib/redundancy'
6import { logger } from '../../../helpers/logger' 19import { logger } from '../../../helpers/logger'
20import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy'
21import { JobQueue } from '@server/lib/job-queue'
7 22
8const serverRedundancyRouter = express.Router() 23const serverRedundancyRouter = express.Router()
9 24
@@ -14,6 +29,31 @@ serverRedundancyRouter.put('/redundancy/:host',
14 asyncMiddleware(updateRedundancy) 29 asyncMiddleware(updateRedundancy)
15) 30)
16 31
32serverRedundancyRouter.get('/redundancy/videos',
33 authenticate,
34 ensureUserHasRight(UserRight.MANAGE_VIDEOS_REDUNDANCIES),
35 listVideoRedundanciesValidator,
36 paginationValidator,
37 videoRedundanciesSortValidator,
38 setDefaultVideoRedundanciesSort,
39 setDefaultPagination,
40 asyncMiddleware(listVideoRedundancies)
41)
42
43serverRedundancyRouter.post('/redundancy/videos',
44 authenticate,
45 ensureUserHasRight(UserRight.MANAGE_VIDEOS_REDUNDANCIES),
46 addVideoRedundancyValidator,
47 asyncMiddleware(addVideoRedundancy)
48)
49
50serverRedundancyRouter.delete('/redundancy/videos/:redundancyId',
51 authenticate,
52 ensureUserHasRight(UserRight.MANAGE_VIDEOS_REDUNDANCIES),
53 removeVideoRedundancyValidator,
54 asyncMiddleware(removeVideoRedundancyController)
55)
56
17// --------------------------------------------------------------------------- 57// ---------------------------------------------------------------------------
18 58
19export { 59export {
@@ -22,6 +62,42 @@ export {
22 62
23// --------------------------------------------------------------------------- 63// ---------------------------------------------------------------------------
24 64
65async function listVideoRedundancies (req: express.Request, res: express.Response) {
66 const resultList = await VideoRedundancyModel.listForApi({
67 start: req.query.start,
68 count: req.query.count,
69 sort: req.query.sort,
70 target: req.query.target,
71 strategy: req.query.strategy
72 })
73
74 const result = {
75 total: resultList.total,
76 data: resultList.data.map(r => VideoRedundancyModel.toFormattedJSONStatic(r))
77 }
78
79 return res.json(result)
80}
81
82async function addVideoRedundancy (req: express.Request, res: express.Response) {
83 const payload = {
84 videoId: res.locals.onlyVideo.id
85 }
86
87 await JobQueue.Instance.createJobWithPromise({
88 type: 'video-redundancy',
89 payload
90 })
91
92 return res.sendStatus(204)
93}
94
95async function removeVideoRedundancyController (req: express.Request, res: express.Response) {
96 await removeVideoRedundancy(res.locals.videoRedundancy)
97
98 return res.sendStatus(204)
99}
100
25async function updateRedundancy (req: express.Request, res: express.Response) { 101async function updateRedundancy (req: express.Request, res: express.Response) {
26 const server = res.locals.server 102 const server = res.locals.server
27 103
@@ -30,7 +106,7 @@ async function updateRedundancy (req: express.Request, res: express.Response) {
30 await server.save() 106 await server.save()
31 107
32 // Async, could be long 108 // Async, could be long
33 removeRedundancyOf(server.id) 109 removeRedundanciesOfServer(server.id)
34 .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, { err })) 110 .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, { err }))
35 111
36 return res.sendStatus(204) 112 return res.sendStatus(204)
diff --git a/server/controllers/api/server/stats.ts b/server/controllers/api/server/stats.ts
index 3616c074d..6d508a481 100644
--- a/server/controllers/api/server/stats.ts
+++ b/server/controllers/api/server/stats.ts
@@ -10,6 +10,7 @@ import { ROUTE_CACHE_LIFETIME } from '../../../initializers/constants'
10import { cacheRoute } from '../../../middlewares/cache' 10import { cacheRoute } from '../../../middlewares/cache'
11import { VideoFileModel } from '../../../models/video/video-file' 11import { VideoFileModel } from '../../../models/video/video-file'
12import { CONFIG } from '../../../initializers/config' 12import { CONFIG } from '../../../initializers/config'
13import { VideoRedundancyStrategyWithManual } from '@shared/models'
13 14
14const statsRouter = express.Router() 15const statsRouter = express.Router()
15 16
@@ -25,8 +26,15 @@ async function getStats (req: express.Request, res: express.Response) {
25 const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats() 26 const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats()
26 const { totalLocalVideoFilesSize } = await VideoFileModel.getStats() 27 const { totalLocalVideoFilesSize } = await VideoFileModel.getStats()
27 28
29 const strategies: { strategy: VideoRedundancyStrategyWithManual, size: number }[] = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES
30 .map(r => ({
31 strategy: r.strategy,
32 size: r.size
33 }))
34 strategies.push({ strategy: 'manual', size: null })
35
28 const videosRedundancyStats = await Promise.all( 36 const videosRedundancyStats = await Promise.all(
29 CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.map(r => { 37 strategies.map(r => {
30 return VideoRedundancyModel.getStats(r.strategy) 38 return VideoRedundancyModel.getStats(r.strategy)
31 .then(stats => Object.assign(stats, { strategy: r.strategy, totalSize: r.size })) 39 .then(stats => Object.assign(stats, { strategy: r.strategy, totalSize: r.size }))
32 }) 40 })
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index b960e80c1..0b7012537 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -53,8 +53,6 @@ import { Hooks } from '@server/lib/plugins/hooks'
53 53
54const auditLogger = auditLoggerFactory('users') 54const auditLogger = auditLoggerFactory('users')
55 55
56// FIXME: https://github.com/nfriedly/express-rate-limit/issues/138
57// @ts-ignore
58const loginRateLimiter = RateLimit({ 56const loginRateLimiter = RateLimit({
59 windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS, 57 windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS,
60 max: CONFIG.RATES_LIMIT.LOGIN.MAX 58 max: CONFIG.RATES_LIMIT.LOGIN.MAX
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index ac7c62aab..23890e20c 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -39,7 +39,7 @@ meRouter.get('/me',
39) 39)
40meRouter.delete('/me', 40meRouter.delete('/me',
41 authenticate, 41 authenticate,
42 asyncMiddleware(deleteMeValidator), 42 deleteMeValidator,
43 asyncMiddleware(deleteMe) 43 asyncMiddleware(deleteMe)
44) 44)
45 45
@@ -214,7 +214,7 @@ async function updateMe (req: express.Request, res: express.Response) {
214} 214}
215 215
216async function updateMyAvatar (req: express.Request, res: express.Response) { 216async function updateMyAvatar (req: express.Request, res: express.Response) {
217 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] 217 const avatarPhysicalFile = req.files['avatarfile'][0]
218 const user = res.locals.oauth.token.user 218 const user = res.locals.oauth.token.user
219 219
220 const userAccount = await AccountModel.load(user.Account.id) 220 const userAccount = await AccountModel.load(user.Account.id)
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
index 43c4c37d8..888392b8b 100644
--- a/server/controllers/api/users/my-subscriptions.ts
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -19,7 +19,6 @@ import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
19import { VideoFilter } from '../../../../shared/models/videos/video-query.type' 19import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
20import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 20import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
21import { JobQueue } from '../../../lib/job-queue' 21import { JobQueue } from '../../../lib/job-queue'
22import { logger } from '../../../helpers/logger'
23import { sequelizeTypescript } from '../../../initializers/database' 22import { sequelizeTypescript } from '../../../initializers/database'
24 23
25const mySubscriptionsRouter = express.Router() 24const mySubscriptionsRouter = express.Router()
@@ -52,7 +51,7 @@ mySubscriptionsRouter.get('/me/subscriptions',
52mySubscriptionsRouter.post('/me/subscriptions', 51mySubscriptionsRouter.post('/me/subscriptions',
53 authenticate, 52 authenticate,
54 userSubscriptionAddValidator, 53 userSubscriptionAddValidator,
55 asyncMiddleware(addUserSubscription) 54 addUserSubscription
56) 55)
57 56
58mySubscriptionsRouter.get('/me/subscriptions/:uri', 57mySubscriptionsRouter.get('/me/subscriptions/:uri',
@@ -106,7 +105,7 @@ async function areSubscriptionsExist (req: express.Request, res: express.Respons
106 return res.json(existObject) 105 return res.json(existObject)
107} 106}
108 107
109async function addUserSubscription (req: express.Request, res: express.Response) { 108function addUserSubscription (req: express.Request, res: express.Response) {
110 const user = res.locals.oauth.token.User 109 const user = res.locals.oauth.token.User
111 const [ name, host ] = req.body.uri.split('@') 110 const [ name, host ] = req.body.uri.split('@')
112 111
@@ -117,7 +116,6 @@ async function addUserSubscription (req: express.Request, res: express.Response)
117 } 116 }
118 117
119 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) 118 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
120 .catch(err => logger.error('Cannot create follow job for subscription %s.', req.body.uri, err))
121 119
122 return res.status(204).end() 120 return res.status(204).end()
123} 121}
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index e1f37a8fb..a808896ff 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -119,7 +119,7 @@ async function listVideoChannels (req: express.Request, res: express.Response) {
119} 119}
120 120
121async function updateVideoChannelAvatar (req: express.Request, res: express.Response) { 121async function updateVideoChannelAvatar (req: express.Request, res: express.Response) {
122 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] 122 const avatarPhysicalFile = req.files['avatarfile'][0]
123 const videoChannel = res.locals.videoChannel 123 const videoChannel = res.locals.videoChannel
124 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON()) 124 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())
125 125
@@ -232,7 +232,6 @@ async function getVideoChannel (req: express.Request, res: express.Response) {
232 232
233 if (videoChannelWithVideos.isOutdated()) { 233 if (videoChannelWithVideos.isOutdated()) {
234 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannelWithVideos.Actor.url } }) 234 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannelWithVideos.Actor.url } })
235 .catch(err => logger.error('Cannot create AP refresher job for actor %s.', videoChannelWithVideos.Actor.url, { err }))
236 } 235 }
237 236
238 return res.json(videoChannelWithVideos.toFormattedJSON()) 237 return res.json(videoChannelWithVideos.toFormattedJSON())
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts
index d9f0ff925..b51490bf9 100644
--- a/server/controllers/api/video-playlist.ts
+++ b/server/controllers/api/video-playlist.ts
@@ -144,7 +144,6 @@ function getVideoPlaylist (req: express.Request, res: express.Response) {
144 144
145 if (videoPlaylist.isOutdated()) { 145 if (videoPlaylist.isOutdated()) {
146 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } }) 146 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } })
147 .catch(err => logger.error('Cannot create AP refresher job for playlist %s.', videoPlaylist.url, { err }))
148 } 147 }
149 148
150 return res.json(videoPlaylist.toFormattedJSON()) 149 return res.json(videoPlaylist.toFormattedJSON())
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts
index 37481d12f..fd7b165fb 100644
--- a/server/controllers/api/videos/captions.ts
+++ b/server/controllers/api/videos/captions.ts
@@ -66,7 +66,7 @@ async function addVideoCaption (req: express.Request, res: express.Response) {
66 await moveAndProcessCaptionFile(videoCaptionPhysicalFile, videoCaption) 66 await moveAndProcessCaptionFile(videoCaptionPhysicalFile, videoCaption)
67 67
68 await sequelizeTypescript.transaction(async t => { 68 await sequelizeTypescript.transaction(async t => {
69 await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, t) 69 await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, null, t)
70 70
71 // Update video update 71 // Update video update
72 await federateVideoIfNeeded(video, false, t) 72 await federateVideoIfNeeded(video, false, t)
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
index 28ced5836..ed223cbc9 100644
--- a/server/controllers/api/videos/import.ts
+++ b/server/controllers/api/videos/import.ts
@@ -88,12 +88,12 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
88 const buf = await readFile(torrentfile.path) 88 const buf = await readFile(torrentfile.path)
89 const parsedTorrent = parseTorrent(buf) 89 const parsedTorrent = parseTorrent(buf)
90 90
91 videoName = isArray(parsedTorrent.name) ? parsedTorrent.name[ 0 ] : parsedTorrent.name as string 91 videoName = isArray(parsedTorrent.name) ? parsedTorrent.name[0] : parsedTorrent.name as string
92 } else { 92 } else {
93 magnetUri = body.magnetUri 93 magnetUri = body.magnetUri
94 94
95 const parsed = magnetUtil.decode(magnetUri) 95 const parsed = magnetUtil.decode(magnetUri)
96 videoName = isArray(parsed.name) ? parsed.name[ 0 ] : parsed.name as string 96 videoName = isArray(parsed.name) ? parsed.name[0] : parsed.name as string
97 } 97 }
98 98
99 const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName }) 99 const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName })
@@ -124,7 +124,7 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
124 videoImportId: videoImport.id, 124 videoImportId: videoImport.id,
125 magnetUri 125 magnetUri
126 } 126 }
127 await JobQueue.Instance.createJob({ type: 'video-import', payload }) 127 await JobQueue.Instance.createJobWithPromise({ type: 'video-import', payload })
128 128
129 auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON())) 129 auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON()))
130 130
@@ -176,7 +176,7 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
176 downloadThumbnail: !thumbnailModel, 176 downloadThumbnail: !thumbnailModel,
177 downloadPreview: !previewModel 177 downloadPreview: !previewModel
178 } 178 }
179 await JobQueue.Instance.createJob({ type: 'video-import', payload }) 179 await JobQueue.Instance.createJobWithPromise({ type: 'video-import', payload })
180 180
181 auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON())) 181 auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON()))
182 182
@@ -211,7 +211,7 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You
211async function processThumbnail (req: express.Request, video: VideoModel) { 211async function processThumbnail (req: express.Request, video: VideoModel) {
212 const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined 212 const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined
213 if (thumbnailField) { 213 if (thumbnailField) {
214 const thumbnailPhysicalFile = thumbnailField[ 0 ] 214 const thumbnailPhysicalFile = thumbnailField[0]
215 215
216 return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE, false) 216 return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE, false)
217 } 217 }
@@ -231,12 +231,12 @@ async function processPreview (req: express.Request, video: VideoModel) {
231} 231}
232 232
233function insertIntoDB (parameters: { 233function insertIntoDB (parameters: {
234 video: MVideoThumbnailAccountDefault, 234 video: MVideoThumbnailAccountDefault
235 thumbnailModel: MThumbnail, 235 thumbnailModel: MThumbnail
236 previewModel: MThumbnail, 236 previewModel: MThumbnail
237 videoChannel: MChannelAccountDefault, 237 videoChannel: MChannelAccountDefault
238 tags: string[], 238 tags: string[]
239 videoImportAttributes: Partial<MVideoImport>, 239 videoImportAttributes: Partial<MVideoImport>
240 user: MUser 240 user: MUser
241}): Bluebird<MVideoImportFormattable> { 241}): Bluebird<MVideoImportFormattable> {
242 const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters 242 const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 8d4ff07eb..eb46ea01f 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -135,7 +135,7 @@ videosRouter.get('/:id',
135 asyncMiddleware(getVideo) 135 asyncMiddleware(getVideo)
136) 136)
137videosRouter.post('/:id/views', 137videosRouter.post('/:id/views',
138 asyncMiddleware(videosGetValidator), 138 asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
139 asyncMiddleware(viewVideo) 139 asyncMiddleware(viewVideo)
140) 140)
141 141
@@ -307,7 +307,7 @@ async function addVideo (req: express.Request, res: express.Response) {
307 } 307 }
308 } 308 }
309 309
310 await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) 310 await JobQueue.Instance.createJobWithPromise({ type: 'video-transcoding', payload: dataInput })
311 } 311 }
312 312
313 Hooks.runAction('action:api.video.uploaded', { video: videoCreated }) 313 Hooks.runAction('action:api.video.uploaded', { video: videoCreated })
@@ -452,14 +452,13 @@ async function getVideo (req: express.Request, res: express.Response) {
452 452
453 if (video.isOutdated()) { 453 if (video.isOutdated()) {
454 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: video.url } }) 454 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: video.url } })
455 .catch(err => logger.error('Cannot create AP refresher job for video %s.', video.url, { err }))
456 } 455 }
457 456
458 return res.json(video.toFormattedDetailsJSON()) 457 return res.json(video.toFormattedDetailsJSON())
459} 458}
460 459
461async function viewVideo (req: express.Request, res: express.Response) { 460async function viewVideo (req: express.Request, res: express.Response) {
462 const videoInstance = res.locals.videoAll 461 const videoInstance = res.locals.onlyImmutableVideo
463 462
464 const ip = req.ip 463 const ip = req.ip
465 const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid) 464 const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid)
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index dc3ff18fc..e4643e171 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -66,7 +66,7 @@ export {
66 66
67// --------------------------------------------------------------------------- 67// ---------------------------------------------------------------------------
68 68
69async function serveServerTranslations (req: express.Request, res: express.Response) { 69function serveServerTranslations (req: express.Request, res: express.Response) {
70 const locale = req.params.locale 70 const locale = req.params.locale
71 const file = req.params.file 71 const file = req.params.file
72 72
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index a4bb3a4d9..4c6cf9597 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -45,12 +45,12 @@ staticRouter.use(
45staticRouter.use( 45staticRouter.use(
46 STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+).torrent', 46 STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+).torrent',
47 asyncMiddleware(videosDownloadValidator), 47 asyncMiddleware(videosDownloadValidator),
48 asyncMiddleware(downloadTorrent) 48 downloadTorrent
49) 49)
50staticRouter.use( 50staticRouter.use(
51 STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+)-hls.torrent', 51 STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+)-hls.torrent',
52 asyncMiddleware(videosDownloadValidator), 52 asyncMiddleware(videosDownloadValidator),
53 asyncMiddleware(downloadHLSVideoFileTorrent) 53 downloadHLSVideoFileTorrent
54) 54)
55 55
56// Videos path for webseeding 56// Videos path for webseeding
@@ -68,13 +68,13 @@ staticRouter.use(
68staticRouter.use( 68staticRouter.use(
69 STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension', 69 STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension',
70 asyncMiddleware(videosDownloadValidator), 70 asyncMiddleware(videosDownloadValidator),
71 asyncMiddleware(downloadVideoFile) 71 downloadVideoFile
72) 72)
73 73
74staticRouter.use( 74staticRouter.use(
75 STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension', 75 STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension',
76 asyncMiddleware(videosDownloadValidator), 76 asyncMiddleware(videosDownloadValidator),
77 asyncMiddleware(downloadHLSVideoFile) 77 downloadHLSVideoFile
78) 78)
79 79
80// HLS 80// HLS
@@ -325,7 +325,7 @@ async function generateNodeinfo (req: express.Request, res: express.Response) {
325 return res.send(json).end() 325 return res.send(json).end()
326} 326}
327 327
328async function downloadTorrent (req: express.Request, res: express.Response) { 328function downloadTorrent (req: express.Request, res: express.Response) {
329 const video = res.locals.videoAll 329 const video = res.locals.videoAll
330 330
331 const videoFile = getVideoFile(req, video.VideoFiles) 331 const videoFile = getVideoFile(req, video.VideoFiles)
@@ -334,7 +334,7 @@ async function downloadTorrent (req: express.Request, res: express.Response) {
334 return res.download(getTorrentFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p.torrent`) 334 return res.download(getTorrentFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p.torrent`)
335} 335}
336 336
337async function downloadHLSVideoFileTorrent (req: express.Request, res: express.Response) { 337function downloadHLSVideoFileTorrent (req: express.Request, res: express.Response) {
338 const video = res.locals.videoAll 338 const video = res.locals.videoAll
339 339
340 const playlist = getHLSPlaylist(video) 340 const playlist = getHLSPlaylist(video)
@@ -346,7 +346,7 @@ async function downloadHLSVideoFileTorrent (req: express.Request, res: express.R
346 return res.download(getTorrentFilePath(playlist, videoFile), `${video.name}-${videoFile.resolution}p-hls.torrent`) 346 return res.download(getTorrentFilePath(playlist, videoFile), `${video.name}-${videoFile.resolution}p-hls.torrent`)
347} 347}
348 348
349async function downloadVideoFile (req: express.Request, res: express.Response) { 349function downloadVideoFile (req: express.Request, res: express.Response) {
350 const video = res.locals.videoAll 350 const video = res.locals.videoAll
351 351
352 const videoFile = getVideoFile(req, video.VideoFiles) 352 const videoFile = getVideoFile(req, video.VideoFiles)
@@ -355,7 +355,7 @@ async function downloadVideoFile (req: express.Request, res: express.Response) {
355 return res.download(getVideoFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`) 355 return res.download(getVideoFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`)
356} 356}
357 357
358async function downloadHLSVideoFile (req: express.Request, res: express.Response) { 358function downloadHLSVideoFile (req: express.Request, res: express.Response) {
359 const video = res.locals.videoAll 359 const video = res.locals.videoAll
360 const playlist = getHLSPlaylist(video) 360 const playlist = getHLSPlaylist(video)
361 if (!playlist) return res.status(404).end 361 if (!playlist) return res.status(404).end
diff --git a/server/controllers/tracker.ts b/server/controllers/tracker.ts
index 2ae1cf86c..e9c8a13da 100644
--- a/server/controllers/tracker.ts
+++ b/server/controllers/tracker.ts
@@ -6,7 +6,6 @@ import * as proxyAddr from 'proxy-addr'
6import { Server as WebSocketServer } from 'ws' 6import { Server as WebSocketServer } from 'ws'
7import { TRACKER_RATE_LIMITS } from '../initializers/constants' 7import { TRACKER_RATE_LIMITS } from '../initializers/constants'
8import { VideoFileModel } from '../models/video/video-file' 8import { VideoFileModel } from '../models/video/video-file'
9import { parse } from 'url'
10import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' 9import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
11import { CONFIG } from '../initializers/config' 10import { CONFIG } from '../initializers/config'
12 11
@@ -38,11 +37,11 @@ const trackerServer = new TrackerServer({
38 37
39 const key = ip + '-' + infoHash 38 const key = ip + '-' + infoHash
40 39
41 peersIps[ ip ] = peersIps[ ip ] ? peersIps[ ip ] + 1 : 1 40 peersIps[ip] = peersIps[ip] ? peersIps[ip] + 1 : 1
42 peersIpInfoHash[ key ] = peersIpInfoHash[ key ] ? peersIpInfoHash[ key ] + 1 : 1 41 peersIpInfoHash[key] = peersIpInfoHash[key] ? peersIpInfoHash[key] + 1 : 1
43 42
44 if (CONFIG.TRACKER.REJECT_TOO_MANY_ANNOUNCES && peersIpInfoHash[ key ] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) { 43 if (CONFIG.TRACKER.REJECT_TOO_MANY_ANNOUNCES && peersIpInfoHash[key] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) {
45 return cb(new Error(`Too many requests (${peersIpInfoHash[ key ]} of ip ${ip} for torrent ${infoHash}`)) 44 return cb(new Error(`Too many requests (${peersIpInfoHash[key]} of ip ${ip} for torrent ${infoHash}`))
46 } 45 }
47 46
48 try { 47 try {
@@ -87,10 +86,8 @@ function createWebsocketTrackerServer (app: express.Application) {
87 trackerServer.onWebSocketConnection(ws) 86 trackerServer.onWebSocketConnection(ws)
88 }) 87 })
89 88
90 server.on('upgrade', (request, socket, head) => { 89 server.on('upgrade', (request: express.Request, socket, head) => {
91 const pathname = parse(request.url).pathname 90 if (request.path === '/tracker/socket') {
92
93 if (pathname === '/tracker/socket') {
94 wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request)) 91 wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request))
95 } 92 }
96 93
diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts
index fc9575160..77c851880 100644
--- a/server/controllers/webfinger.ts
+++ b/server/controllers/webfinger.ts
@@ -18,7 +18,7 @@ export {
18// --------------------------------------------------------------------------- 18// ---------------------------------------------------------------------------
19 19
20function webfingerController (req: express.Request, res: express.Response) { 20function webfingerController (req: express.Request, res: express.Response) {
21 const actor = res.locals.actorFull 21 const actor = res.locals.actorUrl
22 22
23 const json = { 23 const json = {
24 subject: req.query.resource, 24 subject: req.query.resource,
@@ -32,5 +32,5 @@ function webfingerController (req: express.Request, res: express.Response) {
32 ] 32 ]
33 } 33 }
34 34
35 return res.json(json).end() 35 return res.json(json)
36} 36}
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts
index 239d8291d..2d49e6869 100644
--- a/server/helpers/activitypub.ts
+++ b/server/helpers/activitypub.ts
@@ -2,21 +2,36 @@ import * as Bluebird from 'bluebird'
2import validator from 'validator' 2import validator from 'validator'
3import { ResultList } from '../../shared/models' 3import { ResultList } from '../../shared/models'
4import { Activity } from '../../shared/models/activitypub' 4import { Activity } from '../../shared/models/activitypub'
5import { ACTIVITY_PUB } from '../initializers/constants' 5import { ACTIVITY_PUB, REMOTE_SCHEME } from '../initializers/constants'
6import { signJsonLDObject } from './peertube-crypto' 6import { signJsonLDObject } from './peertube-crypto'
7import { pageToStartAndCount } from './core-utils' 7import { pageToStartAndCount } from './core-utils'
8import { parse } from 'url' 8import { URL } from 'url'
9import { MActor } from '../typings/models' 9import { MActor, MVideoAccountLight } from '../typings/models'
10 10
11function activityPubContextify <T> (data: T) { 11export type ContextType = 'All' | 'View' | 'Announce' | 'CacheFile'
12 return Object.assign(data, { 12
13 '@context': [ 13function getContextData (type: ContextType) {
14 'https://www.w3.org/ns/activitystreams', 14 const context: any[] = [
15 'https://w3id.org/security/v1', 15 'https://www.w3.org/ns/activitystreams',
16 { 16 'https://w3id.org/security/v1',
17 RsaSignature2017: 'https://w3id.org/security#RsaSignature2017', 17 {
18 pt: 'https://joinpeertube.org/ns#', 18 RsaSignature2017: 'https://w3id.org/security#RsaSignature2017'
19 sc: 'http://schema.org#', 19 }
20 ]
21
22 if (type !== 'View' && type !== 'Announce') {
23 const additional = {
24 pt: 'https://joinpeertube.org/ns#',
25 sc: 'http://schema.org#'
26 }
27
28 if (type === 'CacheFile') {
29 Object.assign(additional, {
30 expires: 'sc:expires',
31 CacheFile: 'pt:CacheFile'
32 })
33 } else {
34 Object.assign(additional, {
20 Hashtag: 'as:Hashtag', 35 Hashtag: 'as:Hashtag',
21 uuid: 'sc:identifier', 36 uuid: 'sc:identifier',
22 category: 'sc:category', 37 category: 'sc:category',
@@ -24,8 +39,7 @@ function activityPubContextify <T> (data: T) {
24 subtitleLanguage: 'sc:subtitleLanguage', 39 subtitleLanguage: 'sc:subtitleLanguage',
25 sensitive: 'as:sensitive', 40 sensitive: 'as:sensitive',
26 language: 'sc:inLanguage', 41 language: 'sc:inLanguage',
27 expires: 'sc:expires', 42
28 CacheFile: 'pt:CacheFile',
29 Infohash: 'pt:Infohash', 43 Infohash: 'pt:Infohash',
30 originallyPublishedAt: 'sc:datePublished', 44 originallyPublishedAt: 'sc:datePublished',
31 views: { 45 views: {
@@ -71,9 +85,7 @@ function activityPubContextify <T> (data: T) {
71 support: { 85 support: {
72 '@type': 'sc:Text', 86 '@type': 'sc:Text',
73 '@id': 'pt:support' 87 '@id': 'pt:support'
74 } 88 },
75 },
76 {
77 likes: { 89 likes: {
78 '@id': 'as:likes', 90 '@id': 'as:likes',
79 '@type': '@id' 91 '@type': '@id'
@@ -94,9 +106,19 @@ function activityPubContextify <T> (data: T) {
94 '@id': 'as:comments', 106 '@id': 'as:comments',
95 '@type': '@id' 107 '@type': '@id'
96 } 108 }
97 } 109 })
98 ] 110 }
99 }) 111
112 context.push(additional)
113 }
114
115 return {
116 '@context': context
117 }
118}
119
120function activityPubContextify <T> (data: T, type: ContextType = 'All') {
121 return Object.assign({}, data, getContextData(type))
100} 122}
101 123
102type ActivityPubCollectionPaginationHandler = (start: number, count: number) => Bluebird<ResultList<any>> | Promise<ResultList<any>> 124type ActivityPubCollectionPaginationHandler = (start: number, count: number) => Bluebird<ResultList<any>> | Promise<ResultList<any>>
@@ -148,8 +170,8 @@ async function activityPubCollectionPagination (
148 170
149} 171}
150 172
151function buildSignedActivity (byActor: MActor, data: Object) { 173function buildSignedActivity (byActor: MActor, data: Object, contextType?: ContextType) {
152 const activity = activityPubContextify(data) 174 const activity = activityPubContextify(data, contextType)
153 175
154 return signJsonLDObject(byActor, activity) as Promise<Activity> 176 return signJsonLDObject(byActor, activity) as Promise<Activity>
155} 177}
@@ -161,12 +183,18 @@ function getAPId (activity: string | { id: string }) {
161} 183}
162 184
163function checkUrlsSameHost (url1: string, url2: string) { 185function checkUrlsSameHost (url1: string, url2: string) {
164 const idHost = parse(url1).host 186 const idHost = new URL(url1).host
165 const actorHost = parse(url2).host 187 const actorHost = new URL(url2).host
166 188
167 return idHost && actorHost && idHost.toLowerCase() === actorHost.toLowerCase() 189 return idHost && actorHost && idHost.toLowerCase() === actorHost.toLowerCase()
168} 190}
169 191
192function buildRemoteVideoBaseUrl (video: MVideoAccountLight, path: string) {
193 const host = video.VideoChannel.Account.Actor.Server.host
194
195 return REMOTE_SCHEME.HTTP + '://' + host + path
196}
197
170// --------------------------------------------------------------------------- 198// ---------------------------------------------------------------------------
171 199
172export { 200export {
@@ -174,5 +202,6 @@ export {
174 getAPId, 202 getAPId,
175 activityPubContextify, 203 activityPubContextify,
176 activityPubCollectionPagination, 204 activityPubCollectionPagination,
177 buildSignedActivity 205 buildSignedActivity,
206 buildRemoteVideoBaseUrl
178} 207}
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts
index 9b258dc3a..a4cfeef76 100644
--- a/server/helpers/audit-logger.ts
+++ b/server/helpers/audit-logger.ts
@@ -81,7 +81,8 @@ function auditLoggerFactory (domain: string) {
81} 81}
82 82
83abstract class EntityAuditView { 83abstract class EntityAuditView {
84 constructor (private keysToKeep: Array<string>, private prefix: string, private entityInfos: object) { } 84 constructor (private readonly keysToKeep: string[], private readonly prefix: string, private readonly entityInfos: object) { }
85
85 toLogKeys (): object { 86 toLogKeys (): object {
86 return chain(flatten(this.entityInfos, { delimiter: '-', safe: true })) 87 return chain(flatten(this.entityInfos, { delimiter: '-', safe: true }))
87 .pick(this.keysToKeep) 88 .pick(this.keysToKeep)
@@ -121,7 +122,7 @@ const videoKeysToKeep = [
121 'downloadEnabled' 122 'downloadEnabled'
122] 123]
123class VideoAuditView extends EntityAuditView { 124class VideoAuditView extends EntityAuditView {
124 constructor (private video: VideoDetails) { 125 constructor (private readonly video: VideoDetails) {
125 super(videoKeysToKeep, 'video', video) 126 super(videoKeysToKeep, 'video', video)
126 } 127 }
127} 128}
@@ -132,7 +133,7 @@ const videoImportKeysToKeep = [
132 'video-name' 133 'video-name'
133] 134]
134class VideoImportAuditView extends EntityAuditView { 135class VideoImportAuditView extends EntityAuditView {
135 constructor (private videoImport: VideoImport) { 136 constructor (private readonly videoImport: VideoImport) {
136 super(videoImportKeysToKeep, 'video-import', videoImport) 137 super(videoImportKeysToKeep, 'video-import', videoImport)
137 } 138 }
138} 139}
@@ -151,7 +152,7 @@ const commentKeysToKeep = [
151 'account-name' 152 'account-name'
152] 153]
153class CommentAuditView extends EntityAuditView { 154class CommentAuditView extends EntityAuditView {
154 constructor (private comment: VideoComment) { 155 constructor (private readonly comment: VideoComment) {
155 super(commentKeysToKeep, 'comment', comment) 156 super(commentKeysToKeep, 'comment', comment)
156 } 157 }
157} 158}
@@ -180,7 +181,7 @@ const userKeysToKeep = [
180 'videoChannels' 181 'videoChannels'
181] 182]
182class UserAuditView extends EntityAuditView { 183class UserAuditView extends EntityAuditView {
183 constructor (private user: User) { 184 constructor (private readonly user: User) {
184 super(userKeysToKeep, 'user', user) 185 super(userKeysToKeep, 'user', user)
185 } 186 }
186} 187}
@@ -206,7 +207,7 @@ const channelKeysToKeep = [
206 'ownerAccount-displayedName' 207 'ownerAccount-displayedName'
207] 208]
208class VideoChannelAuditView extends EntityAuditView { 209class VideoChannelAuditView extends EntityAuditView {
209 constructor (private channel: VideoChannel) { 210 constructor (private readonly channel: VideoChannel) {
210 super(channelKeysToKeep, 'channel', channel) 211 super(channelKeysToKeep, 'channel', channel)
211 } 212 }
212} 213}
@@ -221,7 +222,7 @@ const videoAbuseKeysToKeep = [
221 'createdAt' 222 'createdAt'
222] 223]
223class VideoAbuseAuditView extends EntityAuditView { 224class VideoAbuseAuditView extends EntityAuditView {
224 constructor (private videoAbuse: VideoAbuse) { 225 constructor (private readonly videoAbuse: VideoAbuse) {
225 super(videoAbuseKeysToKeep, 'abuse', videoAbuse) 226 super(videoAbuseKeysToKeep, 'abuse', videoAbuse)
226 } 227 }
227} 228}
@@ -253,9 +254,12 @@ class CustomConfigAuditView extends EntityAuditView {
253 const infos: any = customConfig 254 const infos: any = customConfig
254 const resolutionsDict = infos.transcoding.resolutions 255 const resolutionsDict = infos.transcoding.resolutions
255 const resolutionsArray = [] 256 const resolutionsArray = []
256 Object.entries(resolutionsDict).forEach(([resolution, isEnabled]) => { 257
257 if (isEnabled) resolutionsArray.push(resolution) 258 Object.entries(resolutionsDict)
258 }) 259 .forEach(([ resolution, isEnabled ]) => {
260 if (isEnabled) resolutionsArray.push(resolution)
261 })
262
259 Object.assign({}, infos, { transcoding: { resolutions: resolutionsArray } }) 263 Object.assign({}, infos, { transcoding: { resolutions: resolutionsArray } })
260 super(customConfigKeysToKeep, 'config', infos) 264 super(customConfigKeysToKeep, 'config', infos)
261 } 265 }
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts
index 7e8252aa4..b1f5d9610 100644
--- a/server/helpers/core-utils.ts
+++ b/server/helpers/core-utils.ts
@@ -1,9 +1,11 @@
1/* eslint-disable no-useless-call */
2
1/* 3/*
2 Different from 'utils' because we don't not import other PeerTube modules. 4 Different from 'utils' because we don't import other PeerTube modules.
3 Useful to avoid circular dependencies. 5 Useful to avoid circular dependencies.
4*/ 6*/
5 7
6import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto' 8import { createHash, HexBase64Latin1Encoding, randomBytes } from 'crypto'
7import { basename, isAbsolute, join, resolve } from 'path' 9import { basename, isAbsolute, join, resolve } from 'path'
8import * as pem from 'pem' 10import * as pem from 'pem'
9import { URL } from 'url' 11import { URL } from 'url'
@@ -22,31 +24,31 @@ const objectConverter = (oldObject: any, keyConverter: (e: string) => string, va
22 const newObject = {} 24 const newObject = {}
23 Object.keys(oldObject).forEach(oldKey => { 25 Object.keys(oldObject).forEach(oldKey => {
24 const newKey = keyConverter(oldKey) 26 const newKey = keyConverter(oldKey)
25 newObject[ newKey ] = objectConverter(oldObject[ oldKey ], keyConverter, valueConverter) 27 newObject[newKey] = objectConverter(oldObject[oldKey], keyConverter, valueConverter)
26 }) 28 })
27 29
28 return newObject 30 return newObject
29} 31}
30 32
31const timeTable = { 33const timeTable = {
32 ms: 1, 34 ms: 1,
33 second: 1000, 35 second: 1000,
34 minute: 60000, 36 minute: 60000,
35 hour: 3600000, 37 hour: 3600000,
36 day: 3600000 * 24, 38 day: 3600000 * 24,
37 week: 3600000 * 24 * 7, 39 week: 3600000 * 24 * 7,
38 month: 3600000 * 24 * 30 40 month: 3600000 * 24 * 30
39} 41}
40 42
41export function parseDurationToMs (duration: number | string): number { 43export function parseDurationToMs (duration: number | string): number {
42 if (typeof duration === 'number') return duration 44 if (typeof duration === 'number') return duration
43 45
44 if (typeof duration === 'string') { 46 if (typeof duration === 'string') {
45 const split = duration.match(/^([\d\.,]+)\s?(\w+)$/) 47 const split = duration.match(/^([\d.,]+)\s?(\w+)$/)
46 48
47 if (split.length === 3) { 49 if (split.length === 3) {
48 const len = parseFloat(split[1]) 50 const len = parseFloat(split[1])
49 let unit = split[2].replace(/s$/i,'').toLowerCase() 51 let unit = split[2].replace(/s$/i, '').toLowerCase()
50 if (unit === 'm') { 52 if (unit === 'm') {
51 unit = 'ms' 53 unit = 'ms'
52 } 54 }
@@ -73,21 +75,21 @@ export function parseBytes (value: string | number): number {
73 75
74 if (value.match(tgm)) { 76 if (value.match(tgm)) {
75 match = value.match(tgm) 77 match = value.match(tgm)
76 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 78 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
77 + parseInt(match[2], 10) * 1024 * 1024 * 1024 79 parseInt(match[2], 10) * 1024 * 1024 * 1024 +
78 + parseInt(match[3], 10) * 1024 * 1024 80 parseInt(match[3], 10) * 1024 * 1024
79 } else if (value.match(tg)) { 81 } else if (value.match(tg)) {
80 match = value.match(tg) 82 match = value.match(tg)
81 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 83 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
82 + parseInt(match[2], 10) * 1024 * 1024 * 1024 84 parseInt(match[2], 10) * 1024 * 1024 * 1024
83 } else if (value.match(tm)) { 85 } else if (value.match(tm)) {
84 match = value.match(tm) 86 match = value.match(tm)
85 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 87 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
86 + parseInt(match[2], 10) * 1024 * 1024 88 parseInt(match[2], 10) * 1024 * 1024
87 } else if (value.match(gm)) { 89 } else if (value.match(gm)) {
88 match = value.match(gm) 90 match = value.match(gm)
89 return parseInt(match[1], 10) * 1024 * 1024 * 1024 91 return parseInt(match[1], 10) * 1024 * 1024 * 1024 +
90 + parseInt(match[2], 10) * 1024 * 1024 92 parseInt(match[2], 10) * 1024 * 1024
91 } else if (value.match(t)) { 93 } else if (value.match(t)) {
92 match = value.match(t) 94 match = value.match(t)
93 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 95 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
@@ -137,6 +139,7 @@ function getAppNumber () {
137} 139}
138 140
139let rootPath: string 141let rootPath: string
142
140function root () { 143function root () {
141 if (rootPath) return rootPath 144 if (rootPath) return rootPath
142 145
@@ -163,7 +166,7 @@ function escapeHTML (stringParam) {
163 '=': '&#x3D;' 166 '=': '&#x3D;'
164 } 167 }
165 168
166 return String(stringParam).replace(/[&<>"'`=\/]/g, s => entityMap[s]) 169 return String(stringParam).replace(/[&<>"'`=/]/g, s => entityMap[s])
167} 170}
168 171
169function pageToStartAndCount (page: number, itemsPerPage: number) { 172function pageToStartAndCount (page: number, itemsPerPage: number) {
@@ -202,6 +205,7 @@ function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex')
202function execShell (command: string, options?: ExecOptions) { 205function execShell (command: string, options?: ExecOptions) {
203 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => { 206 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => {
204 exec(command, options, (err, stdout, stderr) => { 207 exec(command, options, (err, stdout, stderr) => {
208 // eslint-disable-next-line prefer-promise-reject-errors
205 if (err) return rej({ err, stdout, stderr }) 209 if (err) return rej({ err, stdout, stderr })
206 210
207 return res({ stdout, stderr }) 211 return res({ stdout, stderr })
@@ -226,14 +230,6 @@ function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) =>
226 } 230 }
227} 231}
228 232
229function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
230 return function promisified (arg: T): Promise<void> {
231 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
232 func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
233 })
234 }
235}
236
237function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> { 233function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
238 return function promisified (arg1: T, arg2: U): Promise<A> { 234 return function promisified (arg1: T, arg2: U): Promise<A> {
239 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { 235 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
@@ -242,15 +238,7 @@ function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A)
242 } 238 }
243} 239}
244 240
245function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> { 241const randomBytesPromise = promisify1<number, Buffer>(randomBytes)
246 return function promisified (arg1: T, arg2: U): Promise<void> {
247 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
248 func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
249 })
250 }
251}
252
253const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
254const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey) 242const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
255const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey) 243const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
256const execPromise2 = promisify2<string, any, string>(exec) 244const execPromise2 = promisify2<string, any, string>(exec)
@@ -280,7 +268,7 @@ export {
280 promisify1, 268 promisify1,
281 promisify2, 269 promisify2,
282 270
283 pseudoRandomBytesPromise, 271 randomBytesPromise,
284 createPrivateKey, 272 createPrivateKey,
285 getPublicKey, 273 getPublicKey,
286 execPromise2, 274 execPromise2,
diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts
index a407a9fec..749c50cb3 100644
--- a/server/helpers/custom-jsonld-signature.ts
+++ b/server/helpers/custom-jsonld-signature.ts
@@ -5,52 +5,52 @@ import { logger } from './logger'
5const CACHE = { 5const CACHE = {
6 'https://w3id.org/security/v1': { 6 'https://w3id.org/security/v1': {
7 '@context': { 7 '@context': {
8 'id': '@id', 8 id: '@id',
9 'type': '@type', 9 type: '@type',
10 10
11 'dc': 'http://purl.org/dc/terms/', 11 dc: 'http://purl.org/dc/terms/',
12 'sec': 'https://w3id.org/security#', 12 sec: 'https://w3id.org/security#',
13 'xsd': 'http://www.w3.org/2001/XMLSchema#', 13 xsd: 'http://www.w3.org/2001/XMLSchema#',
14 14
15 'EcdsaKoblitzSignature2016': 'sec:EcdsaKoblitzSignature2016', 15 EcdsaKoblitzSignature2016: 'sec:EcdsaKoblitzSignature2016',
16 'Ed25519Signature2018': 'sec:Ed25519Signature2018', 16 Ed25519Signature2018: 'sec:Ed25519Signature2018',
17 'EncryptedMessage': 'sec:EncryptedMessage', 17 EncryptedMessage: 'sec:EncryptedMessage',
18 'GraphSignature2012': 'sec:GraphSignature2012', 18 GraphSignature2012: 'sec:GraphSignature2012',
19 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', 19 LinkedDataSignature2015: 'sec:LinkedDataSignature2015',
20 'LinkedDataSignature2016': 'sec:LinkedDataSignature2016', 20 LinkedDataSignature2016: 'sec:LinkedDataSignature2016',
21 'CryptographicKey': 'sec:Key', 21 CryptographicKey: 'sec:Key',
22 22
23 'authenticationTag': 'sec:authenticationTag', 23 authenticationTag: 'sec:authenticationTag',
24 'canonicalizationAlgorithm': 'sec:canonicalizationAlgorithm', 24 canonicalizationAlgorithm: 'sec:canonicalizationAlgorithm',
25 'cipherAlgorithm': 'sec:cipherAlgorithm', 25 cipherAlgorithm: 'sec:cipherAlgorithm',
26 'cipherData': 'sec:cipherData', 26 cipherData: 'sec:cipherData',
27 'cipherKey': 'sec:cipherKey', 27 cipherKey: 'sec:cipherKey',
28 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, 28 created: { '@id': 'dc:created', '@type': 'xsd:dateTime' },
29 'creator': { '@id': 'dc:creator', '@type': '@id' }, 29 creator: { '@id': 'dc:creator', '@type': '@id' },
30 'digestAlgorithm': 'sec:digestAlgorithm', 30 digestAlgorithm: 'sec:digestAlgorithm',
31 'digestValue': 'sec:digestValue', 31 digestValue: 'sec:digestValue',
32 'domain': 'sec:domain', 32 domain: 'sec:domain',
33 'encryptionKey': 'sec:encryptionKey', 33 encryptionKey: 'sec:encryptionKey',
34 'expiration': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, 34 expiration: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' },
35 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, 35 expires: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' },
36 'initializationVector': 'sec:initializationVector', 36 initializationVector: 'sec:initializationVector',
37 'iterationCount': 'sec:iterationCount', 37 iterationCount: 'sec:iterationCount',
38 'nonce': 'sec:nonce', 38 nonce: 'sec:nonce',
39 'normalizationAlgorithm': 'sec:normalizationAlgorithm', 39 normalizationAlgorithm: 'sec:normalizationAlgorithm',
40 'owner': { '@id': 'sec:owner', '@type': '@id' }, 40 owner: { '@id': 'sec:owner', '@type': '@id' },
41 'password': 'sec:password', 41 password: 'sec:password',
42 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, 42 privateKey: { '@id': 'sec:privateKey', '@type': '@id' },
43 'privateKeyPem': 'sec:privateKeyPem', 43 privateKeyPem: 'sec:privateKeyPem',
44 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, 44 publicKey: { '@id': 'sec:publicKey', '@type': '@id' },
45 'publicKeyBase58': 'sec:publicKeyBase58', 45 publicKeyBase58: 'sec:publicKeyBase58',
46 'publicKeyPem': 'sec:publicKeyPem', 46 publicKeyPem: 'sec:publicKeyPem',
47 'publicKeyWif': 'sec:publicKeyWif', 47 publicKeyWif: 'sec:publicKeyWif',
48 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, 48 publicKeyService: { '@id': 'sec:publicKeyService', '@type': '@id' },
49 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, 49 revoked: { '@id': 'sec:revoked', '@type': 'xsd:dateTime' },
50 'salt': 'sec:salt', 50 salt: 'sec:salt',
51 'signature': 'sec:signature', 51 signature: 'sec:signature',
52 'signatureAlgorithm': 'sec:signingAlgorithm', 52 signatureAlgorithm: 'sec:signingAlgorithm',
53 'signatureValue': 'sec:signatureValue' 53 signatureValue: 'sec:signatureValue'
54 } 54 }
55 } 55 }
56} 56}
@@ -60,12 +60,12 @@ const nodeDocumentLoader = jsonld.documentLoaders.node()
60const lru = new AsyncLRU({ 60const lru = new AsyncLRU({
61 max: 10, 61 max: 10,
62 load: (url, cb) => { 62 load: (url, cb) => {
63 if (CACHE[ url ] !== undefined) { 63 if (CACHE[url] !== undefined) {
64 logger.debug('Using cache for JSON-LD %s.', url) 64 logger.debug('Using cache for JSON-LD %s.', url)
65 65
66 return cb(null, { 66 return cb(null, {
67 contextUrl: null, 67 contextUrl: null,
68 document: CACHE[ url ], 68 document: CACHE[url],
69 documentUrl: url 69 documentUrl: url
70 }) 70 })
71 } 71 }
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts
index fa58e163f..fec67823d 100644
--- a/server/helpers/custom-validators/activitypub/actor.ts
+++ b/server/helpers/custom-validators/activitypub/actor.ts
@@ -6,7 +6,7 @@ import { isHostValid } from '../servers'
6import { peertubeTruncate } from '@server/helpers/core-utils' 6import { peertubeTruncate } from '@server/helpers/core-utils'
7 7
8function isActorEndpointsObjectValid (endpointObject: any) { 8function isActorEndpointsObjectValid (endpointObject: any) {
9 if (endpointObject && endpointObject.sharedInbox) { 9 if (endpointObject?.sharedInbox) {
10 return isActivityPubUrlValid(endpointObject.sharedInbox) 10 return isActivityPubUrlValid(endpointObject.sharedInbox)
11 } 11 }
12 12
@@ -101,8 +101,6 @@ function normalizeActor (actor: any) {
101 actor.summary = null 101 actor.summary = null
102 } 102 }
103 } 103 }
104
105 return
106} 104}
107 105
108function isValidActorHandle (handle: string) { 106function isValidActorHandle (handle: string) {
diff --git a/server/helpers/custom-validators/activitypub/cache-file.ts b/server/helpers/custom-validators/activitypub/cache-file.ts
index 21d5c53ca..c5b3b4d9f 100644
--- a/server/helpers/custom-validators/activitypub/cache-file.ts
+++ b/server/helpers/custom-validators/activitypub/cache-file.ts
@@ -6,7 +6,7 @@ import { CacheFileObject } from '../../../../shared/models/activitypub/objects'
6function isCacheFileObjectValid (object: CacheFileObject) { 6function isCacheFileObjectValid (object: CacheFileObject) {
7 return exists(object) && 7 return exists(object) &&
8 object.type === 'CacheFile' && 8 object.type === 'CacheFile' &&
9 isDateValid(object.expires) && 9 (object.expires === null || isDateValid(object.expires)) &&
10 isActivityPubUrlValid(object.object) && 10 isActivityPubUrlValid(object.object) &&
11 (isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url)) 11 (isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url))
12} 12}
diff --git a/server/helpers/custom-validators/activitypub/video-comments.ts b/server/helpers/custom-validators/activitypub/video-comments.ts
index aa3c246b5..ea852c491 100644
--- a/server/helpers/custom-validators/activitypub/video-comments.ts
+++ b/server/helpers/custom-validators/activitypub/video-comments.ts
@@ -48,8 +48,6 @@ function normalizeComment (comment: any) {
48 if (typeof comment.url === 'object') comment.url = comment.url.href || comment.url.url 48 if (typeof comment.url === 'object') comment.url = comment.url.href || comment.url.url
49 else comment.url = comment.id 49 else comment.url = comment.id
50 } 50 }
51
52 return
53} 51}
54 52
55function isCommentTypeValid (comment: any): boolean { 53function isCommentTypeValid (comment: any): boolean {
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts
index fe94bd58a..22b5e14a2 100644
--- a/server/helpers/custom-validators/activitypub/videos.ts
+++ b/server/helpers/custom-validators/activitypub/videos.ts
@@ -51,11 +51,16 @@ function sanitizeAndCheckVideoTorrentObject (video: any) {
51 logger.debug('Video has invalid captions', { video }) 51 logger.debug('Video has invalid captions', { video })
52 return false 52 return false
53 } 53 }
54 if (!setValidRemoteIcon(video)) {
55 logger.debug('Video has invalid icons', { video })
56 return false
57 }
54 58
55 // Default attributes 59 // Default attributes
56 if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED 60 if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED
57 if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false 61 if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false
58 if (!isBooleanValid(video.downloadEnabled)) video.downloadEnabled = true 62 if (!isBooleanValid(video.downloadEnabled)) video.downloadEnabled = true
63 if (!isBooleanValid(video.commentsEnabled)) video.commentsEnabled = false
59 64
60 return isActivityPubUrlValid(video.id) && 65 return isActivityPubUrlValid(video.id) &&
61 isVideoNameValid(video.name) && 66 isVideoNameValid(video.name) &&
@@ -72,7 +77,6 @@ function sanitizeAndCheckVideoTorrentObject (video: any) {
72 isDateValid(video.updated) && 77 isDateValid(video.updated) &&
73 (!video.originallyPublishedAt || isDateValid(video.originallyPublishedAt)) && 78 (!video.originallyPublishedAt || isDateValid(video.originallyPublishedAt)) &&
74 (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && 79 (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) &&
75 isRemoteVideoIconValid(video.icon) &&
76 video.url.length !== 0 && 80 video.url.length !== 0 &&
77 video.attributedTo.length !== 0 81 video.attributedTo.length !== 0
78} 82}
@@ -131,6 +135,8 @@ function setValidRemoteCaptions (video: any) {
131 if (Array.isArray(video.subtitleLanguage) === false) return false 135 if (Array.isArray(video.subtitleLanguage) === false) return false
132 136
133 video.subtitleLanguage = video.subtitleLanguage.filter(caption => { 137 video.subtitleLanguage = video.subtitleLanguage.filter(caption => {
138 if (!isActivityPubUrlValid(caption.url)) caption.url = null
139
134 return isRemoteStringIdentifierValid(caption) 140 return isRemoteStringIdentifierValid(caption)
135 }) 141 })
136 142
@@ -149,12 +155,19 @@ function isRemoteVideoContentValid (mediaType: string, content: string) {
149 return mediaType === 'text/markdown' && isVideoTruncatedDescriptionValid(content) 155 return mediaType === 'text/markdown' && isVideoTruncatedDescriptionValid(content)
150} 156}
151 157
152function isRemoteVideoIconValid (icon: any) { 158function setValidRemoteIcon (video: any) {
153 return icon.type === 'Image' && 159 if (video.icon && !isArray(video.icon)) video.icon = [ video.icon ]
154 isActivityPubUrlValid(icon.url) && 160 if (!video.icon) video.icon = []
155 icon.mediaType === 'image/jpeg' && 161
156 validator.isInt(icon.width + '', { min: 0 }) && 162 video.icon = video.icon.filter(icon => {
157 validator.isInt(icon.height + '', { min: 0 }) 163 return icon.type === 'Image' &&
164 isActivityPubUrlValid(icon.url) &&
165 icon.mediaType === 'image/jpeg' &&
166 validator.isInt(icon.width + '', { min: 0 }) &&
167 validator.isInt(icon.height + '', { min: 0 })
168 })
169
170 return video.icon.length !== 0
158} 171}
159 172
160function setValidRemoteVideoUrls (video: any) { 173function setValidRemoteVideoUrls (video: any) {
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts
index 89149b3e0..cf32201c4 100644
--- a/server/helpers/custom-validators/misc.ts
+++ b/server/helpers/custom-validators/misc.ts
@@ -94,13 +94,13 @@ function isFileValid (
94 if (isArray(files)) return optional 94 if (isArray(files)) return optional
95 95
96 // Should have a file 96 // Should have a file
97 const fileArray = files[ field ] 97 const fileArray = files[field]
98 if (!fileArray || fileArray.length === 0) { 98 if (!fileArray || fileArray.length === 0) {
99 return optional 99 return optional
100 } 100 }
101 101
102 // The file should exist 102 // The file should exist
103 const file = fileArray[ 0 ] 103 const file = fileArray[0]
104 if (!file || !file.originalname) return false 104 if (!file || !file.originalname) return false
105 105
106 // Check size 106 // Check size
diff --git a/server/helpers/custom-validators/plugins.ts b/server/helpers/custom-validators/plugins.ts
index 3af72547b..5a4531f72 100644
--- a/server/helpers/custom-validators/plugins.ts
+++ b/server/helpers/custom-validators/plugins.ts
@@ -14,7 +14,7 @@ function isPluginTypeValid (value: any) {
14function isPluginNameValid (value: string) { 14function isPluginNameValid (value: string) {
15 return exists(value) && 15 return exists(value) &&
16 validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.NAME) && 16 validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.NAME) &&
17 validator.matches(value, /^[a-z\-]+$/) 17 validator.matches(value, /^[a-z-]+$/)
18} 18}
19 19
20function isNpmPluginNameValid (value: string) { 20function isNpmPluginNameValid (value: string) {
@@ -146,8 +146,8 @@ function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginT
146} 146}
147 147
148function isLibraryCodeValid (library: any) { 148function isLibraryCodeValid (library: any) {
149 return typeof library.register === 'function' 149 return typeof library.register === 'function' &&
150 && typeof library.unregister === 'function' 150 typeof library.unregister === 'function'
151} 151}
152 152
153export { 153export {
diff --git a/server/helpers/custom-validators/user-notifications.ts b/server/helpers/custom-validators/user-notifications.ts
index 5a4d10504..8a33b895b 100644
--- a/server/helpers/custom-validators/user-notifications.ts
+++ b/server/helpers/custom-validators/user-notifications.ts
@@ -9,7 +9,8 @@ function isUserNotificationTypeValid (value: any) {
9 9
10function isUserNotificationSettingValid (value: any) { 10function isUserNotificationSettingValid (value: any) {
11 return exists(value) && 11 return exists(value) &&
12 validator.isInt('' + value) && ( 12 validator.isInt('' + value) &&
13 (
13 value === UserNotificationSettingValue.NONE || 14 value === UserNotificationSettingValue.NONE ||
14 value === UserNotificationSettingValue.WEB || 15 value === UserNotificationSettingValue.WEB ||
15 value === UserNotificationSettingValue.EMAIL || 16 value === UserNotificationSettingValue.EMAIL ||
diff --git a/server/helpers/custom-validators/video-abuses.ts b/server/helpers/custom-validators/video-abuses.ts
index a9478c76a..5c7bc6fd9 100644
--- a/server/helpers/custom-validators/video-abuses.ts
+++ b/server/helpers/custom-validators/video-abuses.ts
@@ -1,8 +1,6 @@
1import { Response } from 'express'
2import validator from 'validator' 1import validator from 'validator'
3import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' 2import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants'
4import { exists } from './misc' 3import { exists } from './misc'
5import { VideoAbuseModel } from '../../models/video/video-abuse'
6 4
7const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES 5const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
8 6
@@ -15,7 +13,7 @@ function isVideoAbuseModerationCommentValid (value: string) {
15} 13}
16 14
17function isVideoAbuseStateValid (value: string) { 15function isVideoAbuseStateValid (value: string) {
18 return exists(value) && VIDEO_ABUSE_STATES[ value ] !== undefined 16 return exists(value) && VIDEO_ABUSE_STATES[value] !== undefined
19} 17}
20 18
21// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
diff --git a/server/helpers/custom-validators/video-captions.ts b/server/helpers/custom-validators/video-captions.ts
index d06eb3695..9abbce04a 100644
--- a/server/helpers/custom-validators/video-captions.ts
+++ b/server/helpers/custom-validators/video-captions.ts
@@ -2,7 +2,7 @@ import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initialize
2import { exists, isFileValid } from './misc' 2import { exists, isFileValid } from './misc'
3 3
4function isVideoCaptionLanguageValid (value: any) { 4function isVideoCaptionLanguageValid (value: any) {
5 return exists(value) && VIDEO_LANGUAGES[ value ] !== undefined 5 return exists(value) && VIDEO_LANGUAGES[value] !== undefined
6} 6}
7 7
8const videoCaptionTypes = Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT) 8const videoCaptionTypes = Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT)
diff --git a/server/helpers/custom-validators/video-imports.ts b/server/helpers/custom-validators/video-imports.ts
index ffad482b4..c571f5ddd 100644
--- a/server/helpers/custom-validators/video-imports.ts
+++ b/server/helpers/custom-validators/video-imports.ts
@@ -20,7 +20,7 @@ function isVideoImportTargetUrlValid (url: string) {
20} 20}
21 21
22function isVideoImportStateValid (value: any) { 22function isVideoImportStateValid (value: any) {
23 return exists(value) && VIDEO_IMPORT_STATES[ value ] !== undefined 23 return exists(value) && VIDEO_IMPORT_STATES[value] !== undefined
24} 24}
25 25
26const videoTorrentImportTypes = Object.keys(MIMETYPES.TORRENT.MIMETYPE_EXT).map(m => `(${m})`) 26const videoTorrentImportTypes = Object.keys(MIMETYPES.TORRENT.MIMETYPE_EXT).map(m => `(${m})`)
diff --git a/server/helpers/custom-validators/video-playlists.ts b/server/helpers/custom-validators/video-playlists.ts
index 4bb8384ab..180018fc5 100644
--- a/server/helpers/custom-validators/video-playlists.ts
+++ b/server/helpers/custom-validators/video-playlists.ts
@@ -1,8 +1,6 @@
1import { exists } from './misc' 1import { exists } from './misc'
2import validator from 'validator' 2import validator from 'validator'
3import { CONSTRAINTS_FIELDS, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PLAYLIST_TYPES } from '../../initializers/constants' 3import { CONSTRAINTS_FIELDS, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PLAYLIST_TYPES } from '../../initializers/constants'
4import * as express from 'express'
5import { VideoPlaylistModel } from '../../models/video/video-playlist'
6 4
7const PLAYLISTS_CONSTRAINT_FIELDS = CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS 5const PLAYLISTS_CONSTRAINT_FIELDS = CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS
8 6
@@ -15,7 +13,7 @@ function isVideoPlaylistDescriptionValid (value: any) {
15} 13}
16 14
17function isVideoPlaylistPrivacyValid (value: number) { 15function isVideoPlaylistPrivacyValid (value: number) {
18 return validator.isInt(value + '') && VIDEO_PLAYLIST_PRIVACIES[ value ] !== undefined 16 return validator.isInt(value + '') && VIDEO_PLAYLIST_PRIVACIES[value] !== undefined
19} 17}
20 18
21function isVideoPlaylistTimestampValid (value: any) { 19function isVideoPlaylistTimestampValid (value: any) {
@@ -23,7 +21,7 @@ function isVideoPlaylistTimestampValid (value: any) {
23} 21}
24 22
25function isVideoPlaylistTypeValid (value: any) { 23function isVideoPlaylistTypeValid (value: any) {
26 return exists(value) && VIDEO_PLAYLIST_TYPES[ value ] !== undefined 24 return exists(value) && VIDEO_PLAYLIST_TYPES[value] !== undefined
27} 25}
28 26
29// --------------------------------------------------------------------------- 27// ---------------------------------------------------------------------------
diff --git a/server/helpers/custom-validators/video-redundancies.ts b/server/helpers/custom-validators/video-redundancies.ts
new file mode 100644
index 000000000..50a559c4f
--- /dev/null
+++ b/server/helpers/custom-validators/video-redundancies.ts
@@ -0,0 +1,12 @@
1import { exists } from './misc'
2
3function isVideoRedundancyTarget (value: any) {
4 return exists(value) &&
5 (value === 'my-videos' || value === 'remote-videos')
6}
7
8// ---------------------------------------------------------------------------
9
10export {
11 isVideoRedundancyTarget
12}
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts
index a9e859e54..cfb430c63 100644
--- a/server/helpers/custom-validators/videos.ts
+++ b/server/helpers/custom-validators/videos.ts
@@ -20,15 +20,15 @@ function isVideoFilterValid (filter: VideoFilter) {
20} 20}
21 21
22function isVideoCategoryValid (value: any) { 22function isVideoCategoryValid (value: any) {
23 return value === null || VIDEO_CATEGORIES[ value ] !== undefined 23 return value === null || VIDEO_CATEGORIES[value] !== undefined
24} 24}
25 25
26function isVideoStateValid (value: any) { 26function isVideoStateValid (value: any) {
27 return exists(value) && VIDEO_STATES[ value ] !== undefined 27 return exists(value) && VIDEO_STATES[value] !== undefined
28} 28}
29 29
30function isVideoLicenceValid (value: any) { 30function isVideoLicenceValid (value: any) {
31 return value === null || VIDEO_LICENCES[ value ] !== undefined 31 return value === null || VIDEO_LICENCES[value] !== undefined
32} 32}
33 33
34function isVideoLanguageValid (value: any) { 34function isVideoLanguageValid (value: any) {
@@ -98,7 +98,7 @@ function isVideoImage (files: { [ fieldname: string ]: Express.Multer.File[] } |
98} 98}
99 99
100function isVideoPrivacyValid (value: number) { 100function isVideoPrivacyValid (value: number) {
101 return VIDEO_PRIVACIES[ value ] !== undefined 101 return VIDEO_PRIVACIES[value] !== undefined
102} 102}
103 103
104function isScheduleVideoUpdatePrivacyValid (value: number) { 104function isScheduleVideoUpdatePrivacyValid (value: number) {
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts
index 9bf6d85a8..f46812977 100644
--- a/server/helpers/express-utils.ts
+++ b/server/helpers/express-utils.ts
@@ -12,7 +12,7 @@ function buildNSFWFilter (res?: express.Response, paramNSFW?: string) {
12 if (paramNSFW === 'false') return false 12 if (paramNSFW === 'false') return false
13 if (paramNSFW === 'both') return undefined 13 if (paramNSFW === 'both') return undefined
14 14
15 if (res && res.locals.oauth) { 15 if (res?.locals.oauth) {
16 const user = res.locals.oauth.token.User 16 const user = res.locals.oauth.token.User
17 17
18 // User does not want NSFW videos 18 // User does not want NSFW videos
@@ -28,7 +28,7 @@ function buildNSFWFilter (res?: express.Response, paramNSFW?: string) {
28 return null 28 return null
29} 29}
30 30
31function cleanUpReqFiles (req: { files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[] }) { 31function cleanUpReqFiles (req: { files: { [fieldname: string]: Express.Multer.File[] } | Express.Multer.File[] }) {
32 const files = req.files 32 const files = req.files
33 33
34 if (!files) return 34 if (!files) return
@@ -39,7 +39,7 @@ function cleanUpReqFiles (req: { files: { [ fieldname: string ]: Express.Multer.
39 } 39 }
40 40
41 for (const key of Object.keys(files)) { 41 for (const key of Object.keys(files)) {
42 const file = files[ key ] 42 const file = files[key]
43 43
44 if (isArray(file)) file.forEach(f => deleteFileAsync(f.path)) 44 if (isArray(file)) file.forEach(f => deleteFileAsync(f.path))
45 else deleteFileAsync(file.path) 45 else deleteFileAsync(file.path)
@@ -65,18 +65,18 @@ function badRequest (req: express.Request, res: express.Response) {
65 65
66function createReqFiles ( 66function createReqFiles (
67 fieldNames: string[], 67 fieldNames: string[],
68 mimeTypes: { [ id: string ]: string }, 68 mimeTypes: { [id: string]: string },
69 destinations: { [ fieldName: string ]: string } 69 destinations: { [fieldName: string]: string }
70) { 70) {
71 const storage = multer.diskStorage({ 71 const storage = multer.diskStorage({
72 destination: (req, file, cb) => { 72 destination: (req, file, cb) => {
73 cb(null, destinations[ file.fieldname ]) 73 cb(null, destinations[file.fieldname])
74 }, 74 },
75 75
76 filename: async (req, file, cb) => { 76 filename: async (req, file, cb) => {
77 let extension: string 77 let extension: string
78 const fileExtension = extname(file.originalname) 78 const fileExtension = extname(file.originalname)
79 const extensionFromMimetype = mimeTypes[ file.mimetype ] 79 const extensionFromMimetype = mimeTypes[file.mimetype]
80 80
81 // Take the file extension if we don't understand the mime type 81 // Take the file extension if we don't understand the mime type
82 // We have the OGG/OGV exception too because firefox sends a bad mime type when sending an OGG file 82 // We have the OGG/OGV exception too because firefox sends a bad mime type when sending an OGG file
@@ -99,7 +99,7 @@ function createReqFiles (
99 } 99 }
100 }) 100 })
101 101
102 let fields: { name: string, maxCount: number }[] = [] 102 const fields: { name: string, maxCount: number }[] = []
103 for (const fieldName of fieldNames) { 103 for (const fieldName of fieldNames) {
104 fields.push({ 104 fields.push({
105 name: fieldName, 105 name: fieldName,
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index 00c32e99a..084516e55 100644
--- a/server/helpers/ffmpeg-utils.ts
+++ b/server/helpers/ffmpeg-utils.ts
@@ -1,6 +1,6 @@
1import * as ffmpeg from 'fluent-ffmpeg' 1import * as ffmpeg from 'fluent-ffmpeg'
2import { dirname, join } from 'path' 2import { dirname, join } from 'path'
3import { getTargetBitrate, getMaxBitrate, VideoResolution } from '../../shared/models/videos' 3import { getMaxBitrate, getTargetBitrate, VideoResolution } from '../../shared/models/videos'
4import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' 4import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants'
5import { processImage } from './image-utils' 5import { processImage } from './image-utils'
6import { logger } from './logger' 6import { logger } from './logger'
@@ -8,6 +8,71 @@ import { checkFFmpegEncoders } from '../initializers/checker-before-init'
8import { readFile, remove, writeFile } from 'fs-extra' 8import { readFile, remove, writeFile } from 'fs-extra'
9import { CONFIG } from '../initializers/config' 9import { CONFIG } from '../initializers/config'
10 10
11/**
12 * A toolbox to play with audio
13 */
14namespace audio {
15 export const get = (videoPath: string) => {
16 // without position, ffprobe considers the last input only
17 // we make it consider the first input only
18 // if you pass a file path to pos, then ffprobe acts on that file directly
19 return new Promise<{ absolutePath: string, audioStream?: any }>((res, rej) => {
20
21 function parseFfprobe (err: any, data: ffmpeg.FfprobeData) {
22 if (err) return rej(err)
23
24 if ('streams' in data) {
25 const audioStream = data.streams.find(stream => stream['codec_type'] === 'audio')
26 if (audioStream) {
27 return res({
28 absolutePath: data.format.filename,
29 audioStream
30 })
31 }
32 }
33
34 return res({ absolutePath: data.format.filename })
35 }
36
37 return ffmpeg.ffprobe(videoPath, parseFfprobe)
38 })
39 }
40
41 export namespace bitrate {
42 const baseKbitrate = 384
43
44 const toBits = (kbits: number) => kbits * 8000
45
46 export const aac = (bitrate: number): number => {
47 switch (true) {
48 case bitrate > toBits(baseKbitrate):
49 return baseKbitrate
50
51 default:
52 return -1 // we interpret it as a signal to copy the audio stream as is
53 }
54 }
55
56 export const mp3 = (bitrate: number): number => {
57 /*
58 a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac.
59 That's why, when using aac, we can go to lower kbit/sec. The equivalences
60 made here are not made to be accurate, especially with good mp3 encoders.
61 */
62 switch (true) {
63 case bitrate <= toBits(192):
64 return 128
65
66 case bitrate <= toBits(384):
67 return 256
68
69 default:
70 return baseKbitrate
71 }
72 }
73 }
74}
75
11function computeResolutionsToTranscode (videoFileHeight: number) { 76function computeResolutionsToTranscode (videoFileHeight: number) {
12 const resolutionsEnabled: number[] = [] 77 const resolutionsEnabled: number[] = []
13 const configResolutions = CONFIG.TRANSCODING.RESOLUTIONS 78 const configResolutions = CONFIG.TRANSCODING.RESOLUTIONS
@@ -24,7 +89,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
24 ] 89 ]
25 90
26 for (const resolution of resolutions) { 91 for (const resolution of resolutions) {
27 if (configResolutions[ resolution + 'p' ] === true && videoFileHeight > resolution) { 92 if (configResolutions[resolution + 'p'] === true && videoFileHeight > resolution) {
28 resolutionsEnabled.push(resolution) 93 resolutionsEnabled.push(resolution)
29 } 94 }
30 } 95 }
@@ -48,9 +113,9 @@ async function getVideoStreamCodec (path: string) {
48 const videoCodec = videoStream.codec_tag_string 113 const videoCodec = videoStream.codec_tag_string
49 114
50 const baseProfileMatrix = { 115 const baseProfileMatrix = {
51 'High': '6400', 116 High: '6400',
52 'Main': '4D40', 117 Main: '4D40',
53 'Baseline': '42E0' 118 Baseline: '42E0'
54 } 119 }
55 120
56 let baseProfile = baseProfileMatrix[videoStream.profile] 121 let baseProfile = baseProfileMatrix[videoStream.profile]
@@ -91,7 +156,7 @@ async function getVideoFileFPS (path: string) {
91 if (videoStream === null) return 0 156 if (videoStream === null) return 0
92 157
93 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { 158 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
94 const valuesText: string = videoStream[ key ] 159 const valuesText: string = videoStream[key]
95 if (!valuesText) continue 160 if (!valuesText) continue
96 161
97 const [ frames, seconds ] = valuesText.split('/') 162 const [ frames, seconds ] = valuesText.split('/')
@@ -191,7 +256,8 @@ interface OnlyAudioTranscodeOptions extends BaseTranscodeOptions {
191 type: 'only-audio' 256 type: 'only-audio'
192} 257}
193 258
194type TranscodeOptions = HLSTranscodeOptions 259type TranscodeOptions =
260 HLSTranscodeOptions
195 | VideoTranscodeOptions 261 | VideoTranscodeOptions
196 | MergeAudioTranscodeOptions 262 | MergeAudioTranscodeOptions
197 | OnlyAudioTranscodeOptions 263 | OnlyAudioTranscodeOptions
@@ -204,13 +270,13 @@ function transcode (options: TranscodeOptions) {
204 .output(options.outputPath) 270 .output(options.outputPath)
205 271
206 if (options.type === 'quick-transcode') { 272 if (options.type === 'quick-transcode') {
207 command = await buildQuickTranscodeCommand(command) 273 command = buildQuickTranscodeCommand(command)
208 } else if (options.type === 'hls') { 274 } else if (options.type === 'hls') {
209 command = await buildHLSCommand(command, options) 275 command = await buildHLSCommand(command, options)
210 } else if (options.type === 'merge-audio') { 276 } else if (options.type === 'merge-audio') {
211 command = await buildAudioMergeCommand(command, options) 277 command = await buildAudioMergeCommand(command, options)
212 } else if (options.type === 'only-audio') { 278 } else if (options.type === 'only-audio') {
213 command = await buildOnlyAudioCommand(command, options) 279 command = buildOnlyAudioCommand(command, options)
214 } else { 280 } else {
215 command = await buildx264Command(command, options) 281 command = await buildx264Command(command, options)
216 } 282 }
@@ -247,22 +313,27 @@ async function canDoQuickTranscode (path: string): Promise<boolean> {
247 313
248 // check video params 314 // check video params
249 if (videoStream == null) return false 315 if (videoStream == null) return false
250 if (videoStream[ 'codec_name' ] !== 'h264') return false 316 if (videoStream['codec_name'] !== 'h264') return false
251 if (videoStream[ 'pix_fmt' ] !== 'yuv420p') return false 317 if (videoStream['pix_fmt'] !== 'yuv420p') return false
252 if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false 318 if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false
253 if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false 319 if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false
254 320
255 // check audio params (if audio stream exists) 321 // check audio params (if audio stream exists)
256 if (parsedAudio.audioStream) { 322 if (parsedAudio.audioStream) {
257 if (parsedAudio.audioStream[ 'codec_name' ] !== 'aac') return false 323 if (parsedAudio.audioStream['codec_name'] !== 'aac') return false
258 324
259 const maxAudioBitrate = audio.bitrate[ 'aac' ](parsedAudio.audioStream[ 'bit_rate' ]) 325 const maxAudioBitrate = audio.bitrate['aac'](parsedAudio.audioStream['bit_rate'])
260 if (maxAudioBitrate !== -1 && parsedAudio.audioStream[ 'bit_rate' ] > maxAudioBitrate) return false 326 if (maxAudioBitrate !== -1 && parsedAudio.audioStream['bit_rate'] > maxAudioBitrate) return false
261 } 327 }
262 328
263 return true 329 return true
264} 330}
265 331
332function getClosestFramerateStandard (fps: number, type: 'HD_STANDARD' | 'STANDARD'): number {
333 return VIDEO_TRANSCODING_FPS[type].slice(0)
334 .sort((a, b) => fps % a - fps % b)[0]
335}
336
266// --------------------------------------------------------------------------- 337// ---------------------------------------------------------------------------
267 338
268export { 339export {
@@ -286,13 +357,14 @@ export {
286 357
287async function buildx264Command (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) { 358async function buildx264Command (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) {
288 let fps = await getVideoFileFPS(options.inputPath) 359 let fps = await getVideoFileFPS(options.inputPath)
289 // On small/medium resolutions, limit FPS
290 if ( 360 if (
361 // On small/medium resolutions, limit FPS
291 options.resolution !== undefined && 362 options.resolution !== undefined &&
292 options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && 363 options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN &&
293 fps > VIDEO_TRANSCODING_FPS.AVERAGE 364 fps > VIDEO_TRANSCODING_FPS.AVERAGE
294 ) { 365 ) {
295 fps = VIDEO_TRANSCODING_FPS.AVERAGE 366 // Get closest standard framerate by modulo: downsampling has to be done to a divisor of the nominal fps value
367 fps = getClosestFramerateStandard(fps, 'STANDARD')
296 } 368 }
297 369
298 command = await presetH264(command, options.inputPath, options.resolution, fps) 370 command = await presetH264(command, options.inputPath, options.resolution, fps)
@@ -305,7 +377,7 @@ async function buildx264Command (command: ffmpeg.FfmpegCommand, options: Transco
305 377
306 if (fps) { 378 if (fps) {
307 // Hard FPS limits 379 // Hard FPS limits
308 if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX 380 if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = getClosestFramerateStandard(fps, 'HD_STANDARD')
309 else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN 381 else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN
310 382
311 command = command.withFPS(fps) 383 command = command.withFPS(fps)
@@ -327,14 +399,14 @@ async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: M
327 return command 399 return command
328} 400}
329 401
330async function buildOnlyAudioCommand (command: ffmpeg.FfmpegCommand, options: OnlyAudioTranscodeOptions) { 402function buildOnlyAudioCommand (command: ffmpeg.FfmpegCommand, options: OnlyAudioTranscodeOptions) {
331 command = await presetOnlyAudio(command) 403 command = presetOnlyAudio(command)
332 404
333 return command 405 return command
334} 406}
335 407
336async function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) { 408function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) {
337 command = await presetCopy(command) 409 command = presetCopy(command)
338 410
339 command = command.outputOption('-map_metadata -1') // strip all metadata 411 command = command.outputOption('-map_metadata -1') // strip all metadata
340 .outputOption('-movflags faststart') 412 .outputOption('-movflags faststart')
@@ -345,7 +417,7 @@ async function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) {
345async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) { 417async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) {
346 const videoPath = getHLSVideoPath(options) 418 const videoPath = getHLSVideoPath(options)
347 419
348 if (options.copyCodecs) command = await presetCopy(command) 420 if (options.copyCodecs) command = presetCopy(command)
349 else command = await buildx264Command(command, options) 421 else command = await buildx264Command(command, options)
350 422
351 command = command.outputOption('-hls_time 4') 423 command = command.outputOption('-hls_time 4')
@@ -413,71 +485,6 @@ async function presetH264VeryFast (command: ffmpeg.FfmpegCommand, input: string,
413} 485}
414 486
415/** 487/**
416 * A toolbox to play with audio
417 */
418namespace audio {
419 export const get = (videoPath: string) => {
420 // without position, ffprobe considers the last input only
421 // we make it consider the first input only
422 // if you pass a file path to pos, then ffprobe acts on that file directly
423 return new Promise<{ absolutePath: string, audioStream?: any }>((res, rej) => {
424
425 function parseFfprobe (err: any, data: ffmpeg.FfprobeData) {
426 if (err) return rej(err)
427
428 if ('streams' in data) {
429 const audioStream = data.streams.find(stream => stream[ 'codec_type' ] === 'audio')
430 if (audioStream) {
431 return res({
432 absolutePath: data.format.filename,
433 audioStream
434 })
435 }
436 }
437
438 return res({ absolutePath: data.format.filename })
439 }
440
441 return ffmpeg.ffprobe(videoPath, parseFfprobe)
442 })
443 }
444
445 export namespace bitrate {
446 const baseKbitrate = 384
447
448 const toBits = (kbits: number) => kbits * 8000
449
450 export const aac = (bitrate: number): number => {
451 switch (true) {
452 case bitrate > toBits(baseKbitrate):
453 return baseKbitrate
454
455 default:
456 return -1 // we interpret it as a signal to copy the audio stream as is
457 }
458 }
459
460 export const mp3 = (bitrate: number): number => {
461 /*
462 a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac.
463 That's why, when using aac, we can go to lower kbit/sec. The equivalences
464 made here are not made to be accurate, especially with good mp3 encoders.
465 */
466 switch (true) {
467 case bitrate <= toBits(192):
468 return 128
469
470 case bitrate <= toBits(384):
471 return 256
472
473 default:
474 return baseKbitrate
475 }
476 }
477 }
478}
479
480/**
481 * Standard profile, with variable bitrate audio and faststart. 488 * Standard profile, with variable bitrate audio and faststart.
482 * 489 *
483 * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel 490 * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel
@@ -507,10 +514,10 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, input: string, resolut
507 // of course this is far from perfect, but it might save some space in the end 514 // of course this is far from perfect, but it might save some space in the end
508 localCommand = localCommand.audioCodec('aac') 515 localCommand = localCommand.audioCodec('aac')
509 516
510 const audioCodecName = parsedAudio.audioStream[ 'codec_name' ] 517 const audioCodecName = parsedAudio.audioStream['codec_name']
511 518
512 if (audio.bitrate[ audioCodecName ]) { 519 if (audio.bitrate[audioCodecName]) {
513 const bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ]) 520 const bitrate = audio.bitrate[audioCodecName](parsedAudio.audioStream['bit_rate'])
514 if (bitrate !== undefined && bitrate !== -1) localCommand = localCommand.audioBitrate(bitrate) 521 if (bitrate !== undefined && bitrate !== -1) localCommand = localCommand.audioBitrate(bitrate)
515 } 522 }
516 } 523 }
@@ -531,14 +538,14 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, input: string, resolut
531 return localCommand 538 return localCommand
532} 539}
533 540
534async function presetCopy (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> { 541function presetCopy (command: ffmpeg.FfmpegCommand): ffmpeg.FfmpegCommand {
535 return command 542 return command
536 .format('mp4') 543 .format('mp4')
537 .videoCodec('copy') 544 .videoCodec('copy')
538 .audioCodec('copy') 545 .audioCodec('copy')
539} 546}
540 547
541async function presetOnlyAudio (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> { 548function presetOnlyAudio (command: ffmpeg.FfmpegCommand): ffmpeg.FfmpegCommand {
542 return command 549 return command
543 .format('mp4') 550 .format('mp4')
544 .audioCodec('copy') 551 .audioCodec('copy')
diff --git a/server/helpers/logger.ts b/server/helpers/logger.ts
index 395417612..b8ae28b3f 100644
--- a/server/helpers/logger.ts
+++ b/server/helpers/logger.ts
@@ -5,7 +5,7 @@ import * as winston from 'winston'
5import { FileTransportOptions } from 'winston/lib/winston/transports' 5import { FileTransportOptions } from 'winston/lib/winston/transports'
6import { CONFIG } from '../initializers/config' 6import { CONFIG } from '../initializers/config'
7import { omit } from 'lodash' 7import { omit } from 'lodash'
8import { LOG_FILENAME } from '@server/initializers/constants' 8import { LOG_FILENAME } from '../initializers/constants'
9 9
10const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT 10const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
11 11
@@ -27,7 +27,7 @@ function getLoggerReplacer () {
27 if (value instanceof Error) { 27 if (value instanceof Error) {
28 const error = {} 28 const error = {}
29 29
30 Object.getOwnPropertyNames(value).forEach(key => error[ key ] = value[ key ]) 30 Object.getOwnPropertyNames(value).forEach(key => { error[key] = value[key] })
31 31
32 return error 32 return error
33 } 33 }
@@ -98,19 +98,20 @@ function bunyanLogFactory (level: string) {
98 let args: any[] = [] 98 let args: any[] = []
99 args.concat(arguments) 99 args.concat(arguments)
100 100
101 if (arguments[ 0 ] instanceof Error) { 101 if (arguments[0] instanceof Error) {
102 meta = arguments[ 0 ].toString() 102 meta = arguments[0].toString()
103 args = Array.prototype.slice.call(arguments, 1) 103 args = Array.prototype.slice.call(arguments, 1)
104 args.push(meta) 104 args.push(meta)
105 } else if (typeof (args[ 0 ]) !== 'string') { 105 } else if (typeof (args[0]) !== 'string') {
106 meta = arguments[ 0 ] 106 meta = arguments[0]
107 args = Array.prototype.slice.call(arguments, 1) 107 args = Array.prototype.slice.call(arguments, 1)
108 args.push(meta) 108 args.push(meta)
109 } 109 }
110 110
111 logger[ level ].apply(logger, args) 111 logger[level].apply(logger, args)
112 } 112 }
113} 113}
114
114const bunyanLogger = { 115const bunyanLogger = {
115 trace: bunyanLogFactory('debug'), 116 trace: bunyanLogFactory('debug'),
116 debug: bunyanLogFactory('debug'), 117 debug: bunyanLogFactory('debug'),
diff --git a/server/helpers/middlewares/videos.ts b/server/helpers/middlewares/videos.ts
index 74f529804..409f78650 100644
--- a/server/helpers/middlewares/videos.ts
+++ b/server/helpers/middlewares/videos.ts
@@ -2,7 +2,16 @@ import { Response } from 'express'
2import { fetchVideo, VideoFetchType } from '../video' 2import { fetchVideo, VideoFetchType } from '../video'
3import { UserRight } from '../../../shared/models/users' 3import { UserRight } from '../../../shared/models/users'
4import { VideoChannelModel } from '../../models/video/video-channel' 4import { VideoChannelModel } from '../../models/video/video-channel'
5import { MUser, MUserAccountId, MVideoAccountLight, MVideoFullLight, MVideoThumbnail, MVideoWithRights } from '@server/typings/models' 5import {
6 MUser,
7 MUserAccountId,
8 MVideoAccountLight,
9 MVideoFullLight,
10 MVideoIdThumbnail,
11 MVideoImmutable,
12 MVideoThumbnail,
13 MVideoWithRights
14} from '@server/typings/models'
6 15
7async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { 16async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') {
8 const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined 17 const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
@@ -22,8 +31,12 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi
22 res.locals.videoAll = video as MVideoFullLight 31 res.locals.videoAll = video as MVideoFullLight
23 break 32 break
24 33
34 case 'only-immutable-attributes':
35 res.locals.onlyImmutableVideo = video as MVideoImmutable
36 break
37
25 case 'id': 38 case 'id':
26 res.locals.videoId = video 39 res.locals.videoId = video as MVideoIdThumbnail
27 break 40 break
28 41
29 case 'only-video': 42 case 'only-video':
diff --git a/server/helpers/regexp.ts b/server/helpers/regexp.ts
index 2336654b0..cfc2be488 100644
--- a/server/helpers/regexp.ts
+++ b/server/helpers/regexp.ts
@@ -1,8 +1,8 @@
1// Thanks to https://regex101.com 1// Thanks to https://regex101.com
2function regexpCapture (str: string, regex: RegExp, maxIterations = 100) { 2function regexpCapture (str: string, regex: RegExp, maxIterations = 100) {
3 const result: RegExpExecArray[] = []
3 let m: RegExpExecArray 4 let m: RegExpExecArray
4 let i = 0 5 let i = 0
5 let result: RegExpExecArray[] = []
6 6
7 // tslint:disable:no-conditional-assignment 7 // tslint:disable:no-conditional-assignment
8 while ((m = regex.exec(str)) !== null && i < maxIterations) { 8 while ((m = regex.exec(str)) !== null && i < maxIterations) {
diff --git a/server/helpers/register-ts-paths.ts b/server/helpers/register-ts-paths.ts
index e8db369e3..eec7fed3e 100644
--- a/server/helpers/register-ts-paths.ts
+++ b/server/helpers/register-ts-paths.ts
@@ -1,5 +1,5 @@
1import { resolve } from 'path' 1import { resolve } from 'path'
2const tsConfigPaths = require('tsconfig-paths') 2import tsConfigPaths = require('tsconfig-paths')
3 3
4const tsConfig = require('../../tsconfig.json') 4const tsConfig = require('../../tsconfig.json')
5 5
diff --git a/server/helpers/signup.ts b/server/helpers/signup.ts
index 7c73f7c5c..d34ff2db5 100644
--- a/server/helpers/signup.ts
+++ b/server/helpers/signup.ts
@@ -21,7 +21,7 @@ async function isSignupAllowed (): Promise<{ allowed: boolean, errorMessage?: st
21 21
22function isSignupAllowedForCurrentIP (ip: string) { 22function isSignupAllowedForCurrentIP (ip: string) {
23 const addr = ipaddr.parse(ip) 23 const addr = ipaddr.parse(ip)
24 let excludeList = [ 'blacklist' ] 24 const excludeList = [ 'blacklist' ]
25 let matched = '' 25 let matched = ''
26 26
27 // if there is a valid, non-empty whitelist, we exclude all unknown adresses too 27 // if there is a valid, non-empty whitelist, we exclude all unknown adresses too
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index 4c6f200f8..7a4c781cc 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -1,6 +1,6 @@
1import { ResultList } from '../../shared' 1import { ResultList } from '../../shared'
2import { ApplicationModel } from '../models/application/application' 2import { ApplicationModel } from '../models/application/application'
3import { execPromise, execPromise2, pseudoRandomBytesPromise, sha256 } from './core-utils' 3import { execPromise, execPromise2, randomBytesPromise, sha256 } from './core-utils'
4import { logger } from './logger' 4import { logger } from './logger'
5import { join } from 'path' 5import { join } from 'path'
6import { Instance as ParseTorrent } from 'parse-torrent' 6import { Instance as ParseTorrent } from 'parse-torrent'
@@ -14,7 +14,7 @@ function deleteFileAsync (path: string) {
14} 14}
15 15
16async function generateRandomString (size: number) { 16async function generateRandomString (size: number) {
17 const raw = await pseudoRandomBytesPromise(size) 17 const raw = await randomBytesPromise(size)
18 18
19 return raw.toString('hex') 19 return raw.toString('hex')
20} 20}
diff --git a/server/helpers/video.ts b/server/helpers/video.ts
index 5b9c026b1..4fe2a60f0 100644
--- a/server/helpers/video.ts
+++ b/server/helpers/video.ts
@@ -5,13 +5,15 @@ import {
5 MVideoFullLight, 5 MVideoFullLight,
6 MVideoIdThumbnail, 6 MVideoIdThumbnail,
7 MVideoThumbnail, 7 MVideoThumbnail,
8 MVideoWithRights 8 MVideoWithRights,
9 MVideoImmutable
9} from '@server/typings/models' 10} from '@server/typings/models'
10import { Response } from 'express' 11import { Response } from 'express'
11 12
12type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' 13type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' | 'only-immutable-attributes'
13 14
14function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight> 15function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight>
16function fetchVideo (id: number | string, fetchType: 'only-immutable-attributes'): Bluebird<MVideoImmutable>
15function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail> 17function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail>
16function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights> 18function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights>
17function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail> 19function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail>
@@ -19,14 +21,16 @@ function fetchVideo (
19 id: number | string, 21 id: number | string,
20 fetchType: VideoFetchType, 22 fetchType: VideoFetchType,
21 userId?: number 23 userId?: number
22): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> 24): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail | MVideoImmutable>
23function fetchVideo ( 25function fetchVideo (
24 id: number | string, 26 id: number | string,
25 fetchType: VideoFetchType, 27 fetchType: VideoFetchType,
26 userId?: number 28 userId?: number
27): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> { 29): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail | MVideoImmutable> {
28 if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) 30 if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId)
29 31
32 if (fetchType === 'only-immutable-attributes') return VideoModel.loadImmutableAttributes(id)
33
30 if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) 34 if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id)
31 35
32 if (fetchType === 'only-video') return VideoModel.load(id) 36 if (fetchType === 'only-video') return VideoModel.load(id)
@@ -34,14 +38,23 @@ function fetchVideo (
34 if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id) 38 if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id)
35} 39}
36 40
37type VideoFetchByUrlType = 'all' | 'only-video' 41type VideoFetchByUrlType = 'all' | 'only-video' | 'only-immutable-attributes'
38 42
39function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountLightBlacklistAllFiles> 43function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountLightBlacklistAllFiles>
44function fetchVideoByUrl (url: string, fetchType: 'only-immutable-attributes'): Bluebird<MVideoImmutable>
40function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail> 45function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail>
41function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> 46function fetchVideoByUrl (
42function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> { 47 url: string,
48 fetchType: VideoFetchByUrlType
49): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable>
50function fetchVideoByUrl (
51 url: string,
52 fetchType: VideoFetchByUrlType
53): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> {
43 if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url) 54 if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url)
44 55
56 if (fetchType === 'only-immutable-attributes') return VideoModel.loadByUrlImmutableAttributes(url)
57
45 if (fetchType === 'only-video') return VideoModel.loadByUrl(url) 58 if (fetchType === 'only-video') return VideoModel.loadByUrl(url)
46} 59}
47 60
diff --git a/server/helpers/webtorrent.ts b/server/helpers/webtorrent.ts
index 3a99518c6..b25e44fcd 100644
--- a/server/helpers/webtorrent.ts
+++ b/server/helpers/webtorrent.ts
@@ -9,12 +9,12 @@ import { promisify2 } from './core-utils'
9import { MVideo } from '@server/typings/models/video/video' 9import { MVideo } from '@server/typings/models/video/video'
10import { MVideoFile, MVideoFileRedundanciesOpt } from '@server/typings/models/video/video-file' 10import { MVideoFile, MVideoFileRedundanciesOpt } from '@server/typings/models/video/video-file'
11import { isStreamingPlaylist, MStreamingPlaylistVideo } from '@server/typings/models/video/video-streaming-playlist' 11import { isStreamingPlaylist, MStreamingPlaylistVideo } from '@server/typings/models/video/video-streaming-playlist'
12import { STATIC_PATHS, WEBSERVER } from '@server/initializers/constants' 12import { WEBSERVER } from '@server/initializers/constants'
13import * as parseTorrent from 'parse-torrent' 13import * as parseTorrent from 'parse-torrent'
14import * as magnetUtil from 'magnet-uri' 14import * as magnetUtil from 'magnet-uri'
15import { isArray } from '@server/helpers/custom-validators/misc' 15import { isArray } from '@server/helpers/custom-validators/misc'
16import { extractVideo } from '@server/lib/videos' 16import { extractVideo } from '@server/lib/videos'
17import { getTorrentFileName, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 17import { getTorrentFileName, getVideoFilePath } from '@server/lib/video-paths'
18 18
19const createTorrentPromise = promisify2<string, any, any>(createTorrent) 19const createTorrentPromise = promisify2<string, any, any>(createTorrent)
20 20
@@ -39,7 +39,7 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName
39 if (torrent.files.length !== 1) { 39 if (torrent.files.length !== 1) {
40 if (timer) clearTimeout(timer) 40 if (timer) clearTimeout(timer)
41 41
42 for (let file of torrent.files) { 42 for (const file of torrent.files) {
43 deleteDownloadedFile({ directoryPath, filepath: file.path }) 43 deleteDownloadedFile({ directoryPath, filepath: file.path })
44 } 44 }
45 45
@@ -47,15 +47,16 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName
47 .then(() => rej(new Error('Cannot import torrent ' + torrentId + ': there are multiple files in it'))) 47 .then(() => rej(new Error('Cannot import torrent ' + torrentId + ': there are multiple files in it')))
48 } 48 }
49 49
50 file = torrent.files[ 0 ] 50 file = torrent.files[0]
51 51
52 // FIXME: avoid creating another stream when https://github.com/webtorrent/webtorrent/issues/1517 is fixed 52 // FIXME: avoid creating another stream when https://github.com/webtorrent/webtorrent/issues/1517 is fixed
53 const writeStream = createWriteStream(path) 53 const writeStream = createWriteStream(path)
54 writeStream.on('finish', () => { 54 writeStream.on('finish', () => {
55 if (timer) clearTimeout(timer) 55 if (timer) clearTimeout(timer)
56 56
57 return safeWebtorrentDestroy(webtorrent, torrentId, { directoryPath, filepath: file.path }, target.torrentName) 57 safeWebtorrentDestroy(webtorrent, torrentId, { directoryPath, filepath: file.path }, target.torrentName)
58 .then(() => res(path)) 58 .then(() => res(path))
59 .catch(err => logger.error('Cannot destroy webtorrent.', { err }))
59 }) 60 })
60 61
61 file.createReadStream().pipe(writeStream) 62 file.createReadStream().pipe(writeStream)
@@ -63,9 +64,16 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName
63 64
64 torrent.on('error', err => rej(err)) 65 torrent.on('error', err => rej(err))
65 66
66 timer = setTimeout(async () => { 67 timer = setTimeout(() => {
67 return safeWebtorrentDestroy(webtorrent, torrentId, file ? { directoryPath, filepath: file.path } : undefined, target.torrentName) 68 const err = new Error('Webtorrent download timeout.')
68 .then(() => rej(new Error('Webtorrent download timeout.'))) 69
70 safeWebtorrentDestroy(webtorrent, torrentId, file ? { directoryPath, filepath: file.path } : undefined, target.torrentName)
71 .then(() => rej(err))
72 .catch(destroyErr => {
73 logger.error('Cannot destroy webtorrent.', { err: destroyErr })
74 rej(err)
75 })
76
69 }, timeout) 77 }, timeout)
70 }) 78 })
71} 79}
diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts
index 577a59dbf..fc9d416a1 100644
--- a/server/helpers/youtube-dl.ts
+++ b/server/helpers/youtube-dl.ts
@@ -24,20 +24,23 @@ const processOptions = {
24} 24}
25 25
26function getYoutubeDLInfo (url: string, opts?: string[]): Promise<YoutubeDLInfo> { 26function getYoutubeDLInfo (url: string, opts?: string[]): Promise<YoutubeDLInfo> {
27 return new Promise<YoutubeDLInfo>(async (res, rej) => { 27 return new Promise<YoutubeDLInfo>((res, rej) => {
28 let args = opts || [ '-j', '--flat-playlist' ] 28 let args = opts || [ '-j', '--flat-playlist' ]
29 args = wrapWithProxyOptions(args) 29 args = wrapWithProxyOptions(args)
30 30
31 const youtubeDL = await safeGetYoutubeDL() 31 safeGetYoutubeDL()
32 youtubeDL.getInfo(url, args, processOptions, (err, info) => { 32 .then(youtubeDL => {
33 if (err) return rej(err) 33 youtubeDL.getInfo(url, args, processOptions, (err, info) => {
34 if (info.is_live === true) return rej(new Error('Cannot download a live streaming.')) 34 if (err) return rej(err)
35 if (info.is_live === true) return rej(new Error('Cannot download a live streaming.'))
35 36
36 const obj = buildVideoInfo(normalizeObject(info)) 37 const obj = buildVideoInfo(normalizeObject(info))
37 if (obj.name && obj.name.length < CONSTRAINTS_FIELDS.VIDEOS.NAME.min) obj.name += ' video' 38 if (obj.name && obj.name.length < CONSTRAINTS_FIELDS.VIDEOS.NAME.min) obj.name += ' video'
38 39
39 return res(obj) 40 return res(obj)
40 }) 41 })
42 })
43 .catch(err => rej(err))
41 }) 44 })
42} 45}
43 46
@@ -54,26 +57,34 @@ function downloadYoutubeDLVideo (url: string, timeout: number) {
54 options = options.concat([ '--ffmpeg-location', process.env.FFMPEG_PATH ]) 57 options = options.concat([ '--ffmpeg-location', process.env.FFMPEG_PATH ])
55 } 58 }
56 59
57 return new Promise<string>(async (res, rej) => { 60 return new Promise<string>((res, rej) => {
58 const youtubeDL = await safeGetYoutubeDL() 61 safeGetYoutubeDL()
59 youtubeDL.exec(url, options, processOptions, err => { 62 .then(youtubeDL => {
60 clearTimeout(timer) 63 youtubeDL.exec(url, options, processOptions, err => {
64 clearTimeout(timer)
61 65
62 if (err) { 66 if (err) {
63 remove(path) 67 remove(path)
64 .catch(err => logger.error('Cannot delete path on YoutubeDL error.', { err })) 68 .catch(err => logger.error('Cannot delete path on YoutubeDL error.', { err }))
65 69
66 return rej(err) 70 return rej(err)
67 } 71 }
68 72
69 return res(path) 73 return res(path)
70 }) 74 })
71 75
72 timer = setTimeout(async () => { 76 timer = setTimeout(() => {
73 await remove(path) 77 const err = new Error('YoutubeDL download timeout.')
74 78
75 return rej(new Error('YoutubeDL download timeout.')) 79 remove(path)
76 }, timeout) 80 .finally(() => rej(err))
81 .catch(err => {
82 logger.error('Cannot remove %s in youtubeDL timeout.', path, { err })
83 return rej(err)
84 })
85 }, timeout)
86 })
87 .catch(err => rej(err))
77 }) 88 })
78} 89}
79 90
@@ -103,7 +114,7 @@ async function updateYoutubeDLBinary () {
103 114
104 const url = result.headers.location 115 const url = result.headers.location
105 const downloadFile = request.get(url) 116 const downloadFile = request.get(url)
106 const newVersion = /yt-dl\.org\/downloads\/(\d{4}\.\d\d\.\d\d(\.\d)?)\/youtube-dl/.exec(url)[ 1 ] 117 const newVersion = /yt-dl\.org\/downloads\/(\d{4}\.\d\d\.\d\d(\.\d)?)\/youtube-dl/.exec(url)[1]
107 118
108 downloadFile.on('response', result => { 119 downloadFile.on('response', result => {
109 if (result.statusCode !== 200) { 120 if (result.statusCode !== 200) {
diff --git a/server/initializers/checker-after-init.ts b/server/initializers/checker-after-init.ts
index 44efd346c..978023129 100644
--- a/server/initializers/checker-after-init.ts
+++ b/server/initializers/checker-after-init.ts
@@ -3,7 +3,7 @@ import { isProdInstance, isTestInstance } from '../helpers/core-utils'
3import { UserModel } from '../models/account/user' 3import { UserModel } from '../models/account/user'
4import { ApplicationModel } from '../models/application/application' 4import { ApplicationModel } from '../models/application/application'
5import { OAuthClientModel } from '../models/oauth/oauth-client' 5import { OAuthClientModel } from '../models/oauth/oauth-client'
6import { parse } from 'url' 6import { URL } from 'url'
7import { CONFIG } from './config' 7import { CONFIG } from './config'
8import { logger } from '../helpers/logger' 8import { logger } from '../helpers/logger'
9import { getServerActor } from '../helpers/utils' 9import { getServerActor } from '../helpers/utils'
@@ -16,7 +16,7 @@ import { WEBSERVER } from './constants'
16async function checkActivityPubUrls () { 16async function checkActivityPubUrls () {
17 const actor = await getServerActor() 17 const actor = await getServerActor()
18 18
19 const parsed = parse(actor.url) 19 const parsed = new URL(actor.url)
20 if (WEBSERVER.HOST !== parsed.host) { 20 if (WEBSERVER.HOST !== parsed.host) {
21 const NODE_ENV = config.util.getEnv('NODE_ENV') 21 const NODE_ENV = config.util.getEnv('NODE_ENV')
22 const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR') 22 const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR')
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts
index 9731a0af9..a75f2cec2 100644
--- a/server/initializers/checker-before-init.ts
+++ b/server/initializers/checker-before-init.ts
@@ -35,8 +35,8 @@ function checkMissedConfig () {
35 ] 35 ]
36 const requiredAlternatives = [ 36 const requiredAlternatives = [
37 [ // set 37 [ // set
38 ['redis.hostname', 'redis.port'], // alternative 38 [ 'redis.hostname', 'redis.port' ], // alternative
39 ['redis.socket'] 39 [ 'redis.socket' ]
40 ] 40 ]
41 ] 41 ]
42 const miss: string[] = [] 42 const miss: string[] = []
diff --git a/server/initializers/config.ts b/server/initializers/config.ts
index 7fd77f3e8..75372fa4e 100644
--- a/server/initializers/config.ts
+++ b/server/initializers/config.ts
@@ -1,6 +1,6 @@
1import { IConfig } from 'config' 1import { IConfig } from 'config'
2import { dirname, join } from 'path' 2import { dirname, join } from 'path'
3import { VideosRedundancy } from '../../shared/models' 3import { VideosRedundancyStrategy } from '../../shared/models'
4// Do not use barrels, remain constants as independent as possible 4// Do not use barrels, remain constants as independent as possible
5import { buildPath, parseBytes, parseDurationToMs, root } from '../helpers/core-utils' 5import { buildPath, parseBytes, parseDurationToMs, root } from '../helpers/core-utils'
6import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' 6import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
@@ -301,10 +301,10 @@ function getLocalConfigFilePath () {
301 if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}` 301 if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
302 if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}` 302 if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
303 303
304 return join(dirname(configSources[ 0 ].name), filename + '.json') 304 return join(dirname(configSources[0].name), filename + '.json')
305} 305}
306 306
307function buildVideosRedundancy (objs: any[]): VideosRedundancy[] { 307function buildVideosRedundancy (objs: any[]): VideosRedundancyStrategy[] {
308 if (!objs) return [] 308 if (!objs) return []
309 309
310 if (!Array.isArray(objs)) return objs 310 if (!Array.isArray(objs)) return objs
@@ -330,7 +330,7 @@ export function reloadConfig () {
330 330
331 function purge () { 331 function purge () {
332 for (const fileName in require.cache) { 332 for (const fileName in require.cache) {
333 if (-1 === fileName.indexOf(directory())) { 333 if (fileName.indexOf(directory()) === -1) {
334 continue 334 continue
335 } 335 }
336 336
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index e4b8a8eca..0476815f4 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -4,7 +4,7 @@ import { ActivityPubActorType } from '../../shared/models/activitypub'
4import { FollowState } from '../../shared/models/actors' 4import { FollowState } from '../../shared/models/actors'
5import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos' 5import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos'
6// Do not use barrels, remain constants as independent as possible 6// Do not use barrels, remain constants as independent as possible
7import { isTestInstance, sanitizeHost, sanitizeUrl, root, parseDurationToMs } from '../helpers/core-utils' 7import { isTestInstance, sanitizeHost, sanitizeUrl, root } from '../helpers/core-utils'
8import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' 8import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
9import { invert } from 'lodash' 9import { invert } from 'lodash'
10import { CronRepeatOptions, EveryRepeatOptions } from 'bull' 10import { CronRepeatOptions, EveryRepeatOptions } from 'bull'
@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
14 14
15// --------------------------------------------------------------------------- 15// ---------------------------------------------------------------------------
16 16
17const LAST_MIGRATION_VERSION = 470 17const LAST_MIGRATION_VERSION = 480
18 18
19// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
20 20
@@ -73,7 +73,9 @@ const SORTABLE_COLUMNS = {
73 73
74 PLUGINS: [ 'name', 'createdAt', 'updatedAt' ], 74 PLUGINS: [ 'name', 'createdAt', 'updatedAt' ],
75 75
76 AVAILABLE_PLUGINS: [ 'npmName', 'popularity' ] 76 AVAILABLE_PLUGINS: [ 'npmName', 'popularity' ],
77
78 VIDEO_REDUNDANCIES: [ 'name' ]
77} 79}
78 80
79const OAUTH_LIFETIME = { 81const OAUTH_LIFETIME = {
@@ -117,45 +119,44 @@ const REMOTE_SCHEME = {
117 WS: 'wss' 119 WS: 'wss'
118} 120}
119 121
120// TODO: remove 'video-file' 122const JOB_ATTEMPTS: { [id in JobType]: number } = {
121const JOB_ATTEMPTS: { [id in (JobType | 'video-file')]: number } = {
122 'activitypub-http-broadcast': 5, 123 'activitypub-http-broadcast': 5,
123 'activitypub-http-unicast': 5, 124 'activitypub-http-unicast': 5,
124 'activitypub-http-fetcher': 5, 125 'activitypub-http-fetcher': 5,
125 'activitypub-follow': 5, 126 'activitypub-follow': 5,
126 'video-file-import': 1, 127 'video-file-import': 1,
127 'video-transcoding': 1, 128 'video-transcoding': 1,
128 'video-file': 1,
129 'video-import': 1, 129 'video-import': 1,
130 'email': 5, 130 'email': 5,
131 'videos-views': 1, 131 'videos-views': 1,
132 'activitypub-refresher': 1 132 'activitypub-refresher': 1,
133 'video-redundancy': 1
133} 134}
134const JOB_CONCURRENCY: { [id in (JobType | 'video-file')]: number } = { 135const JOB_CONCURRENCY: { [id in JobType]: number } = {
135 'activitypub-http-broadcast': 1, 136 'activitypub-http-broadcast': 1,
136 'activitypub-http-unicast': 5, 137 'activitypub-http-unicast': 5,
137 'activitypub-http-fetcher': 1, 138 'activitypub-http-fetcher': 1,
138 'activitypub-follow': 1, 139 'activitypub-follow': 1,
139 'video-file-import': 1, 140 'video-file-import': 1,
140 'video-transcoding': 1, 141 'video-transcoding': 1,
141 'video-file': 1,
142 'video-import': 1, 142 'video-import': 1,
143 'email': 5, 143 'email': 5,
144 'videos-views': 1, 144 'videos-views': 1,
145 'activitypub-refresher': 1 145 'activitypub-refresher': 1,
146 'video-redundancy': 1
146} 147}
147const JOB_TTL: { [id in (JobType | 'video-file')]: number } = { 148const JOB_TTL: { [id in JobType]: number } = {
148 'activitypub-http-broadcast': 60000 * 10, // 10 minutes 149 'activitypub-http-broadcast': 60000 * 10, // 10 minutes
149 'activitypub-http-unicast': 60000 * 10, // 10 minutes 150 'activitypub-http-unicast': 60000 * 10, // 10 minutes
150 'activitypub-http-fetcher': 60000 * 10, // 10 minutes 151 'activitypub-http-fetcher': 60000 * 10, // 10 minutes
151 'activitypub-follow': 60000 * 10, // 10 minutes 152 'activitypub-follow': 60000 * 10, // 10 minutes
152 'video-file-import': 1000 * 3600, // 1 hour 153 'video-file-import': 1000 * 3600, // 1 hour
153 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long 154 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long
154 'video-file': 1000 * 3600 * 48, // 2 days, transcoding could be long
155 'video-import': 1000 * 3600 * 2, // hours 155 'video-import': 1000 * 3600 * 2, // hours
156 'email': 60000 * 10, // 10 minutes 156 'email': 60000 * 10, // 10 minutes
157 'videos-views': undefined, // Unlimited 157 'videos-views': undefined, // Unlimited
158 'activitypub-refresher': 60000 * 10 // 10 minutes 158 'activitypub-refresher': 60000 * 10, // 10 minutes
159 'video-redundancy': 1000 * 3600 * 3 // 3 hours
159} 160}
160const REPEAT_JOBS: { [ id: string ]: EveryRepeatOptions | CronRepeatOptions } = { 161const REPEAT_JOBS: { [ id: string ]: EveryRepeatOptions | CronRepeatOptions } = {
161 'videos-views': { 162 'videos-views': {
@@ -309,6 +310,8 @@ let CONTACT_FORM_LIFETIME = 60000 * 60 // 1 hour
309 310
310const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = { 311const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = {
311 MIN: 10, 312 MIN: 10,
313 STANDARD: [ 24, 25, 30 ],
314 HD_STANDARD: [ 50, 60 ],
312 AVERAGE: 30, 315 AVERAGE: 30,
313 MAX: 60, 316 MAX: 60,
314 KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum) 317 KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum)
@@ -358,42 +361,42 @@ const VIDEO_LICENCES = {
358 7: 'Public Domain Dedication' 361 7: 'Public Domain Dedication'
359} 362}
360 363
361let VIDEO_LANGUAGES: { [id: string]: string } = {} 364const VIDEO_LANGUAGES: { [id: string]: string } = {}
362 365
363const VIDEO_PRIVACIES = { 366const VIDEO_PRIVACIES = {
364 [ VideoPrivacy.PUBLIC ]: 'Public', 367 [VideoPrivacy.PUBLIC]: 'Public',
365 [ VideoPrivacy.UNLISTED ]: 'Unlisted', 368 [VideoPrivacy.UNLISTED]: 'Unlisted',
366 [ VideoPrivacy.PRIVATE ]: 'Private', 369 [VideoPrivacy.PRIVATE]: 'Private',
367 [ VideoPrivacy.INTERNAL ]: 'Internal' 370 [VideoPrivacy.INTERNAL]: 'Internal'
368} 371}
369 372
370const VIDEO_STATES = { 373const VIDEO_STATES = {
371 [ VideoState.PUBLISHED ]: 'Published', 374 [VideoState.PUBLISHED]: 'Published',
372 [ VideoState.TO_TRANSCODE ]: 'To transcode', 375 [VideoState.TO_TRANSCODE]: 'To transcode',
373 [ VideoState.TO_IMPORT ]: 'To import' 376 [VideoState.TO_IMPORT]: 'To import'
374} 377}
375 378
376const VIDEO_IMPORT_STATES = { 379const VIDEO_IMPORT_STATES = {
377 [ VideoImportState.FAILED ]: 'Failed', 380 [VideoImportState.FAILED]: 'Failed',
378 [ VideoImportState.PENDING ]: 'Pending', 381 [VideoImportState.PENDING]: 'Pending',
379 [ VideoImportState.SUCCESS ]: 'Success' 382 [VideoImportState.SUCCESS]: 'Success'
380} 383}
381 384
382const VIDEO_ABUSE_STATES = { 385const VIDEO_ABUSE_STATES = {
383 [ VideoAbuseState.PENDING ]: 'Pending', 386 [VideoAbuseState.PENDING]: 'Pending',
384 [ VideoAbuseState.REJECTED ]: 'Rejected', 387 [VideoAbuseState.REJECTED]: 'Rejected',
385 [ VideoAbuseState.ACCEPTED ]: 'Accepted' 388 [VideoAbuseState.ACCEPTED]: 'Accepted'
386} 389}
387 390
388const VIDEO_PLAYLIST_PRIVACIES = { 391const VIDEO_PLAYLIST_PRIVACIES = {
389 [ VideoPlaylistPrivacy.PUBLIC ]: 'Public', 392 [VideoPlaylistPrivacy.PUBLIC]: 'Public',
390 [ VideoPlaylistPrivacy.UNLISTED ]: 'Unlisted', 393 [VideoPlaylistPrivacy.UNLISTED]: 'Unlisted',
391 [ VideoPlaylistPrivacy.PRIVATE ]: 'Private' 394 [VideoPlaylistPrivacy.PRIVATE]: 'Private'
392} 395}
393 396
394const VIDEO_PLAYLIST_TYPES = { 397const VIDEO_PLAYLIST_TYPES = {
395 [ VideoPlaylistType.REGULAR ]: 'Regular', 398 [VideoPlaylistType.REGULAR]: 'Regular',
396 [ VideoPlaylistType.WATCH_LATER ]: 'Watch later' 399 [VideoPlaylistType.WATCH_LATER]: 'Watch later'
397} 400}
398 401
399const MIMETYPES = { 402const MIMETYPES = {
@@ -530,7 +533,7 @@ const LAZY_STATIC_PATHS = {
530} 533}
531 534
532// Cache control 535// Cache control
533let STATIC_MAX_AGE = { 536const STATIC_MAX_AGE = {
534 SERVER: '2h', 537 SERVER: '2h',
535 CLIENT: '30d' 538 CLIENT: '30d'
536} 539}
@@ -538,11 +541,13 @@ let STATIC_MAX_AGE = {
538// Videos thumbnail size 541// Videos thumbnail size
539const THUMBNAILS_SIZE = { 542const THUMBNAILS_SIZE = {
540 width: 223, 543 width: 223,
541 height: 122 544 height: 122,
545 minWidth: 150
542} 546}
543const PREVIEWS_SIZE = { 547const PREVIEWS_SIZE = {
544 width: 850, 548 width: 850,
545 height: 480 549 height: 480,
550 minWidth: 400
546} 551}
547const AVATARS_SIZE = { 552const AVATARS_SIZE = {
548 width: 120, 553 width: 120,
@@ -666,14 +671,14 @@ if (isTestInstance() === true) {
666 SCHEDULER_INTERVALS_MS.removeOldViews = 5000 671 SCHEDULER_INTERVALS_MS.removeOldViews = 5000
667 SCHEDULER_INTERVALS_MS.updateVideos = 5000 672 SCHEDULER_INTERVALS_MS.updateVideos = 5000
668 SCHEDULER_INTERVALS_MS.autoFollowIndexInstances = 5000 673 SCHEDULER_INTERVALS_MS.autoFollowIndexInstances = 5000
669 REPEAT_JOBS[ 'videos-views' ] = { every: 5000 } 674 REPEAT_JOBS['videos-views'] = { every: 5000 }
670 675
671 REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 676 REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1
672 677
673 VIDEO_VIEW_LIFETIME = 1000 // 1 second 678 VIDEO_VIEW_LIFETIME = 1000 // 1 second
674 CONTACT_FORM_LIFETIME = 1000 // 1 second 679 CONTACT_FORM_LIFETIME = 1000 // 1 second
675 680
676 JOB_ATTEMPTS[ 'email' ] = 1 681 JOB_ATTEMPTS['email'] = 1
677 682
678 FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000 683 FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000
679 MEMOIZE_TTL.OVERVIEWS_SAMPLE = 1 684 MEMOIZE_TTL.OVERVIEWS_SAMPLE = 1
@@ -832,42 +837,42 @@ function loadLanguages () {
832function buildLanguages () { 837function buildLanguages () {
833 const iso639 = require('iso-639-3') 838 const iso639 = require('iso-639-3')
834 839
835 const languages: { [ id: string ]: string } = {} 840 const languages: { [id: string]: string } = {}
836 841
837 const additionalLanguages = { 842 const additionalLanguages = {
838 'sgn': true, // Sign languages (macro language) 843 sgn: true, // Sign languages (macro language)
839 'ase': true, // American sign language 844 ase: true, // American sign language
840 'sdl': true, // Arabian sign language 845 sdl: true, // Arabian sign language
841 'bfi': true, // British sign language 846 bfi: true, // British sign language
842 'bzs': true, // Brazilian sign language 847 bzs: true, // Brazilian sign language
843 'csl': true, // Chinese sign language 848 csl: true, // Chinese sign language
844 'cse': true, // Czech sign language 849 cse: true, // Czech sign language
845 'dsl': true, // Danish sign language 850 dsl: true, // Danish sign language
846 'fsl': true, // French sign language 851 fsl: true, // French sign language
847 'gsg': true, // German sign language 852 gsg: true, // German sign language
848 'pks': true, // Pakistan sign language 853 pks: true, // Pakistan sign language
849 'jsl': true, // Japanese sign language 854 jsl: true, // Japanese sign language
850 'sfs': true, // South African sign language 855 sfs: true, // South African sign language
851 'swl': true, // Swedish sign language 856 swl: true, // Swedish sign language
852 'rsl': true, // Russian sign language: true 857 rsl: true, // Russian sign language: true
853 858
854 'epo': true, // Esperanto 859 epo: true, // Esperanto
855 'tlh': true, // Klingon 860 tlh: true, // Klingon
856 'jbo': true, // Lojban 861 jbo: true, // Lojban
857 'avk': true // Kotava 862 avk: true // Kotava
858 } 863 }
859 864
860 // Only add ISO639-1 languages and some sign languages (ISO639-3) 865 // Only add ISO639-1 languages and some sign languages (ISO639-3)
861 iso639 866 iso639
862 .filter(l => { 867 .filter(l => {
863 return (l.iso6391 !== null && l.type === 'living') || 868 return (l.iso6391 !== undefined && l.type === 'living') ||
864 additionalLanguages[ l.iso6393 ] === true 869 additionalLanguages[l.iso6393] === true
865 }) 870 })
866 .forEach(l => languages[ l.iso6391 || l.iso6393 ] = l.name) 871 .forEach(l => { languages[l.iso6391 || l.iso6393] = l.name })
867 872
868 // Override Occitan label 873 // Override Occitan label
869 languages[ 'oc' ] = 'Occitan' 874 languages['oc'] = 'Occitan'
870 languages[ 'el' ] = 'Greek' 875 languages['el'] = 'Greek'
871 876
872 return languages 877 return languages
873} 878}
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index 9ec146ab1..eedaf3c4e 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -119,8 +119,6 @@ async function initDatabaseModels (silent: boolean) {
119 await createFunctions() 119 await createFunctions()
120 120
121 if (!silent) logger.info('Database %s is ready.', dbname) 121 if (!silent) logger.info('Database %s is ready.', dbname)
122
123 return
124} 122}
125 123
126// --------------------------------------------------------------------------- 124// ---------------------------------------------------------------------------
diff --git a/server/initializers/migrations/0005-email-pod.ts b/server/initializers/migrations/0005-email-pod.ts
index c34a12255..417c33b1f 100644
--- a/server/initializers/migrations/0005-email-pod.ts
+++ b/server/initializers/migrations/0005-email-pod.ts
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0010-email-user.ts b/server/initializers/migrations/0010-email-user.ts
index 37a7b0bb3..f7d01f6d6 100644
--- a/server/initializers/migrations/0010-email-user.ts
+++ b/server/initializers/migrations/0010-email-user.ts
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0015-video-views.ts b/server/initializers/migrations/0015-video-views.ts
index 25164ff4d..47dd4069b 100644
--- a/server/initializers/migrations/0015-video-views.ts
+++ b/server/initializers/migrations/0015-video-views.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0020-video-likes.ts b/server/initializers/migrations/0020-video-likes.ts
index 945be5a98..44333f3b0 100644
--- a/server/initializers/migrations/0020-video-likes.ts
+++ b/server/initializers/migrations/0020-video-likes.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0025-video-dislikes.ts b/server/initializers/migrations/0025-video-dislikes.ts
index 27144c437..2aa22e2d7 100644
--- a/server/initializers/migrations/0025-video-dislikes.ts
+++ b/server/initializers/migrations/0025-video-dislikes.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0030-video-category.ts b/server/initializers/migrations/0030-video-category.ts
index f784f820d..00cd2d8cf 100644
--- a/server/initializers/migrations/0030-video-category.ts
+++ b/server/initializers/migrations/0030-video-category.ts
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0035-video-licence.ts b/server/initializers/migrations/0035-video-licence.ts
index 3d0b0bac9..61d666c5e 100644
--- a/server/initializers/migrations/0035-video-licence.ts
+++ b/server/initializers/migrations/0035-video-licence.ts
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0040-video-nsfw.ts b/server/initializers/migrations/0040-video-nsfw.ts
index f7f70d3c4..44aec8a6c 100644
--- a/server/initializers/migrations/0040-video-nsfw.ts
+++ b/server/initializers/migrations/0040-video-nsfw.ts
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0045-user-display-nsfw.ts b/server/initializers/migrations/0045-user-display-nsfw.ts
index aef420f0e..07795bd75 100644
--- a/server/initializers/migrations/0045-user-display-nsfw.ts
+++ b/server/initializers/migrations/0045-user-display-nsfw.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0050-video-language.ts b/server/initializers/migrations/0050-video-language.ts
index 796fa5f95..6f90abb44 100644
--- a/server/initializers/migrations/0050-video-language.ts
+++ b/server/initializers/migrations/0050-video-language.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0055-video-uuid.ts b/server/initializers/migrations/0055-video-uuid.ts
index e0f904080..8a58aebb8 100644
--- a/server/initializers/migrations/0055-video-uuid.ts
+++ b/server/initializers/migrations/0055-video-uuid.ts
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0060-video-file.ts b/server/initializers/migrations/0060-video-file.ts
index c362cf71a..00647e60e 100644
--- a/server/initializers/migrations/0060-video-file.ts
+++ b/server/initializers/migrations/0060-video-file.ts
@@ -2,9 +2,9 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize, 7 sequelize: Sequelize.Sequelize
8 db: any 8 db: any
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0065-video-file-size.ts b/server/initializers/migrations/0065-video-file-size.ts
index e9ce77e50..0bdc675c2 100644
--- a/server/initializers/migrations/0065-video-file-size.ts
+++ b/server/initializers/migrations/0065-video-file-size.ts
@@ -5,9 +5,9 @@ import { VideoModel } from '../../models/video/video'
5import { getVideoFilePath } from '@server/lib/video-paths' 5import { getVideoFilePath } from '@server/lib/video-paths'
6 6
7function up (utils: { 7function up (utils: {
8 transaction: Sequelize.Transaction, 8 transaction: Sequelize.Transaction
9 queryInterface: Sequelize.QueryInterface, 9 queryInterface: Sequelize.QueryInterface
10 sequelize: Sequelize.Sequelize, 10 sequelize: Sequelize.Sequelize
11 db: any 11 db: any
12}): Promise<void> { 12}): Promise<void> {
13 return utils.db.Video.listOwnedAndPopulateAuthorAndTags() 13 return utils.db.Video.listOwnedAndPopulateAuthorAndTags()
diff --git a/server/initializers/migrations/0070-user-video-quota.ts b/server/initializers/migrations/0070-user-video-quota.ts
index 37683432f..1d073f244 100644
--- a/server/initializers/migrations/0070-user-video-quota.ts
+++ b/server/initializers/migrations/0070-user-video-quota.ts
@@ -3,9 +3,9 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize, 8 sequelize: Sequelize.Sequelize
9 db: any 9 db: any
10}): Promise<void> { 10}): Promise<void> {
11 const q = utils.queryInterface 11 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0075-video-resolutions.ts b/server/initializers/migrations/0075-video-resolutions.ts
index e4f26cb77..f56c1b2c3 100644
--- a/server/initializers/migrations/0075-video-resolutions.ts
+++ b/server/initializers/migrations/0075-video-resolutions.ts
@@ -5,9 +5,9 @@ import { getVideoFileResolution } from '../../helpers/ffmpeg-utils'
5import { readdir, rename } from 'fs-extra' 5import { readdir, rename } from 'fs-extra'
6 6
7function up (utils: { 7function up (utils: {
8 transaction: Sequelize.Transaction, 8 transaction: Sequelize.Transaction
9 queryInterface: Sequelize.QueryInterface, 9 queryInterface: Sequelize.QueryInterface
10 sequelize: Sequelize.Sequelize, 10 sequelize: Sequelize.Sequelize
11 db: any 11 db: any
12}): Promise<void> { 12}): Promise<void> {
13 const torrentDir = CONFIG.STORAGE.TORRENTS_DIR 13 const torrentDir = CONFIG.STORAGE.TORRENTS_DIR
diff --git a/server/initializers/migrations/0080-video-channels.ts b/server/initializers/migrations/0080-video-channels.ts
index 5512bdcf1..b8e9bd6d0 100644
--- a/server/initializers/migrations/0080-video-channels.ts
+++ b/server/initializers/migrations/0080-video-channels.ts
@@ -2,9 +2,9 @@ import * as Sequelize from 'sequelize'
2import * as uuidv4 from 'uuid/v4' 2import * as uuidv4 from 'uuid/v4'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize, 7 sequelize: Sequelize.Sequelize
8 db: any 8 db: any
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0085-user-role.ts b/server/initializers/migrations/0085-user-role.ts
index de75faec2..ec7428fd5 100644
--- a/server/initializers/migrations/0085-user-role.ts
+++ b/server/initializers/migrations/0085-user-role.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0090-videos-description.ts b/server/initializers/migrations/0090-videos-description.ts
index 6f98dcade..32e518d75 100644
--- a/server/initializers/migrations/0090-videos-description.ts
+++ b/server/initializers/migrations/0090-videos-description.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0095-videos-privacy.ts b/server/initializers/migrations/0095-videos-privacy.ts
index 4c2bf91d0..c732d6f6a 100644
--- a/server/initializers/migrations/0095-videos-privacy.ts
+++ b/server/initializers/migrations/0095-videos-privacy.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0100-activitypub.ts b/server/initializers/migrations/0100-activitypub.ts
index 96d44a7ce..05fd37406 100644
--- a/server/initializers/migrations/0100-activitypub.ts
+++ b/server/initializers/migrations/0100-activitypub.ts
@@ -7,9 +7,9 @@ import { ApplicationModel } from '../../models/application/application'
7import { SERVER_ACTOR_NAME } from '../constants' 7import { SERVER_ACTOR_NAME } from '../constants'
8 8
9async function up (utils: { 9async function up (utils: {
10 transaction: Sequelize.Transaction, 10 transaction: Sequelize.Transaction
11 queryInterface: Sequelize.QueryInterface, 11 queryInterface: Sequelize.QueryInterface
12 sequelize: Sequelize.Sequelize, 12 sequelize: Sequelize.Sequelize
13 db: any 13 db: any
14}): Promise<void> { 14}): Promise<void> {
15 const q = utils.queryInterface 15 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0105-server-mail.ts b/server/initializers/migrations/0105-server-mail.ts
index 4b9600e91..5ee37c418 100644
--- a/server/initializers/migrations/0105-server-mail.ts
+++ b/server/initializers/migrations/0105-server-mail.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 await utils.queryInterface.removeColumn('Servers', 'email') 9 await utils.queryInterface.removeColumn('Servers', 'email')
diff --git a/server/initializers/migrations/0110-server-key.ts b/server/initializers/migrations/0110-server-key.ts
index 5ff6daf69..354cd7e76 100644
--- a/server/initializers/migrations/0110-server-key.ts
+++ b/server/initializers/migrations/0110-server-key.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 await utils.queryInterface.removeColumn('Servers', 'publicKey') 9 await utils.queryInterface.removeColumn('Servers', 'publicKey')
diff --git a/server/initializers/migrations/0115-account-avatar.ts b/server/initializers/migrations/0115-account-avatar.ts
index b318e8163..604b6394a 100644
--- a/server/initializers/migrations/0115-account-avatar.ts
+++ b/server/initializers/migrations/0115-account-avatar.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 await utils.db.Avatar.sync() 9 await utils.db.Avatar.sync()
diff --git a/server/initializers/migrations/0120-video-null.ts b/server/initializers/migrations/0120-video-null.ts
index 6d253f04f..1b407b270 100644
--- a/server/initializers/migrations/0120-video-null.ts
+++ b/server/initializers/migrations/0120-video-null.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 9
diff --git a/server/initializers/migrations/0125-table-lowercase.ts b/server/initializers/migrations/0125-table-lowercase.ts
index 78041ccb0..f75a56791 100644
--- a/server/initializers/migrations/0125-table-lowercase.ts
+++ b/server/initializers/migrations/0125-table-lowercase.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 await utils.queryInterface.renameTable('Applications', 'application') 8 await utils.queryInterface.renameTable('Applications', 'application')
diff --git a/server/initializers/migrations/0130-user-autoplay-video.ts b/server/initializers/migrations/0130-user-autoplay-video.ts
index 9f6878e39..d57934588 100644
--- a/server/initializers/migrations/0130-user-autoplay-video.ts
+++ b/server/initializers/migrations/0130-user-autoplay-video.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0135-video-channel-actor.ts b/server/initializers/migrations/0135-video-channel-actor.ts
index 5ace0f4d2..c0c343384 100644
--- a/server/initializers/migrations/0135-video-channel-actor.ts
+++ b/server/initializers/migrations/0135-video-channel-actor.ts
@@ -3,8 +3,8 @@ import { DataType } from 'sequelize-typescript'
3import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' 3import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
4 4
5async function up (utils: { 5async function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 // Create actor table 10 // Create actor table
@@ -64,10 +64,10 @@ async function up (utils: {
64 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 64 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
65 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 65 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
66 ) 66 )
67 SELECT 67 SELECT
68 'Application', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 68 'Application', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
69 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 69 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
70 FROM account 70 FROM account
71 WHERE "applicationId" IS NOT NULL 71 WHERE "applicationId" IS NOT NULL
72 ` 72 `
73 await utils.sequelize.query(query1) 73 await utils.sequelize.query(query1)
@@ -79,10 +79,10 @@ async function up (utils: {
79 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 79 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
80 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 80 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
81 ) 81 )
82 SELECT 82 SELECT
83 'Person', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 83 'Person', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
84 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 84 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
85 FROM account 85 FROM account
86 WHERE "applicationId" IS NULL 86 WHERE "applicationId" IS NULL
87 ` 87 `
88 await utils.sequelize.query(query2) 88 await utils.sequelize.query(query2)
@@ -108,17 +108,17 @@ async function up (utils: {
108 } 108 }
109 109
110 { 110 {
111 const query = ` 111 const query = `
112 INSERT INTO actor 112 INSERT INTO actor
113 ( 113 (
114 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 114 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
115 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 115 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
116 ) 116 )
117 SELECT 117 SELECT
118 'Group', "videoChannel".uuid, "videoChannel".uuid, "videoChannel".url, null, null, 0, 0, "videoChannel".url || '/inbox', 118 'Group', "videoChannel".uuid, "videoChannel".uuid, "videoChannel".url, null, null, 0, 0, "videoChannel".url || '/inbox',
119 "videoChannel".url || '/outbox', "videoChannel".url || '/inbox', "videoChannel".url || '/followers', "videoChannel".url || '/following', 119 "videoChannel".url || '/outbox', "videoChannel".url || '/inbox', "videoChannel".url || '/followers', "videoChannel".url || '/following',
120 null, account."serverId", "videoChannel"."createdAt", "videoChannel"."updatedAt" 120 null, account."serverId", "videoChannel"."createdAt", "videoChannel"."updatedAt"
121 FROM "videoChannel" 121 FROM "videoChannel"
122 INNER JOIN "account" on "videoChannel"."accountId" = "account".id 122 INNER JOIN "account" on "videoChannel"."accountId" = "account".id
123 ` 123 `
124 await utils.sequelize.query(query) 124 await utils.sequelize.query(query)
@@ -157,13 +157,13 @@ async function up (utils: {
157 } 157 }
158 158
159 { 159 {
160 const query1 = `UPDATE "actorFollow" 160 const query1 = `UPDATE "actorFollow"
161 SET "actorId" = 161 SET "actorId" =
162 (SELECT "account"."actorId" FROM account WHERE "account"."id" = "actorFollow"."actorId")` 162 (SELECT "account"."actorId" FROM account WHERE "account"."id" = "actorFollow"."actorId")`
163 await utils.sequelize.query(query1) 163 await utils.sequelize.query(query1)
164 164
165 const query2 = `UPDATE "actorFollow" 165 const query2 = `UPDATE "actorFollow"
166 SET "targetActorId" = 166 SET "targetActorId" =
167 (SELECT "account"."actorId" FROM account WHERE "account"."id" = "actorFollow"."targetActorId")` 167 (SELECT "account"."actorId" FROM account WHERE "account"."id" = "actorFollow"."targetActorId")`
168 168
169 await utils.sequelize.query(query2) 169 await utils.sequelize.query(query2)
@@ -189,8 +189,8 @@ async function up (utils: {
189 await utils.queryInterface.removeConstraint('videoShare', 'videoShare_accountId_fkey') 189 await utils.queryInterface.removeConstraint('videoShare', 'videoShare_accountId_fkey')
190 } 190 }
191 191
192 const query = `UPDATE "videoShare" 192 const query = `UPDATE "videoShare"
193 SET "actorId" = 193 SET "actorId" =
194 (SELECT "actorId" FROM account WHERE id = "videoShare"."actorId")` 194 (SELECT "actorId" FROM account WHERE id = "videoShare"."actorId")`
195 await utils.sequelize.query(query) 195 await utils.sequelize.query(query)
196 196
diff --git a/server/initializers/migrations/0140-actor-url.ts b/server/initializers/migrations/0140-actor-url.ts
index 020499391..d790988ad 100644
--- a/server/initializers/migrations/0140-actor-url.ts
+++ b/server/initializers/migrations/0140-actor-url.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import { WEBSERVER } from '../constants' 2import { WEBSERVER } from '../constants'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const toReplace = WEBSERVER.HOSTNAME + ':443' 9 const toReplace = WEBSERVER.HOSTNAME + ':443'
diff --git a/server/initializers/migrations/0145-delete-author.ts b/server/initializers/migrations/0145-delete-author.ts
index cb23d1cc2..6c9427997 100644
--- a/server/initializers/migrations/0145-delete-author.ts
+++ b/server/initializers/migrations/0145-delete-author.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 await utils.queryInterface.dropTable('Authors') 8 await utils.queryInterface.dropTable('Authors')
diff --git a/server/initializers/migrations/0150-avatar-cascade.ts b/server/initializers/migrations/0150-avatar-cascade.ts
index 821696717..fb3b25773 100644
--- a/server/initializers/migrations/0150-avatar-cascade.ts
+++ b/server/initializers/migrations/0150-avatar-cascade.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 await utils.queryInterface.removeConstraint('actor', 'actor_avatarId_fkey') 8 await utils.queryInterface.removeConstraint('actor', 'actor_avatarId_fkey')
diff --git a/server/initializers/migrations/0155-video-comments-enabled.ts b/server/initializers/migrations/0155-video-comments-enabled.ts
index 6949d3a0c..691640b35 100644
--- a/server/initializers/migrations/0155-video-comments-enabled.ts
+++ b/server/initializers/migrations/0155-video-comments-enabled.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import { Migration } from '../../models/migrations' 2import { Migration } from '../../models/migrations'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0160-account-route.ts b/server/initializers/migrations/0160-account-route.ts
index cab4c72f1..97469948b 100644
--- a/server/initializers/migrations/0160-account-route.ts
+++ b/server/initializers/migrations/0160-account-route.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 { 8 {
diff --git a/server/initializers/migrations/0165-video-route.ts b/server/initializers/migrations/0165-video-route.ts
index 56d98bc69..aa7c75128 100644
--- a/server/initializers/migrations/0165-video-route.ts
+++ b/server/initializers/migrations/0165-video-route.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 { 8 {
diff --git a/server/initializers/migrations/0170-actor-follow-score.ts b/server/initializers/migrations/0170-actor-follow-score.ts
index a12b35da9..901a3c799 100644
--- a/server/initializers/migrations/0170-actor-follow-score.ts
+++ b/server/initializers/migrations/0170-actor-follow-score.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import { ACTOR_FOLLOW_SCORE } from '../constants' 2import { ACTOR_FOLLOW_SCORE } from '../constants'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 await utils.queryInterface.removeColumn('server', 'score') 9 await utils.queryInterface.removeColumn('server', 'score')
diff --git a/server/initializers/migrations/0175-actor-follow-counts.ts b/server/initializers/migrations/0175-actor-follow-counts.ts
index 4fb524181..d7853f8dc 100644
--- a/server/initializers/migrations/0175-actor-follow-counts.ts
+++ b/server/initializers/migrations/0175-actor-follow-counts.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 const query = 'UPDATE "actor" SET ' + 8 const query = 'UPDATE "actor" SET ' +
diff --git a/server/initializers/migrations/0180-job-table-delete.ts b/server/initializers/migrations/0180-job-table-delete.ts
index df29145d0..fb48a0c9d 100644
--- a/server/initializers/migrations/0180-job-table-delete.ts
+++ b/server/initializers/migrations/0180-job-table-delete.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 await utils.queryInterface.dropTable('job') 8 await utils.queryInterface.dropTable('job')
diff --git a/server/initializers/migrations/0185-video-share-url.ts b/server/initializers/migrations/0185-video-share-url.ts
index f7eeb0878..f59931e0f 100644
--- a/server/initializers/migrations/0185-video-share-url.ts
+++ b/server/initializers/migrations/0185-video-share-url.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 { 8 {
diff --git a/server/initializers/migrations/0190-video-comment-unique-url.ts b/server/initializers/migrations/0190-video-comment-unique-url.ts
index b196c9352..a8769ed41 100644
--- a/server/initializers/migrations/0190-video-comment-unique-url.ts
+++ b/server/initializers/migrations/0190-video-comment-unique-url.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 { 8 {
diff --git a/server/initializers/migrations/0195-support.ts b/server/initializers/migrations/0195-support.ts
index 3b9eabe79..3f7c75dce 100644
--- a/server/initializers/migrations/0195-support.ts
+++ b/server/initializers/migrations/0195-support.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 { 8 {
diff --git a/server/initializers/migrations/0200-video-published-at.ts b/server/initializers/migrations/0200-video-published-at.ts
index 1701ea07a..d8c7b42a7 100644
--- a/server/initializers/migrations/0200-video-published-at.ts
+++ b/server/initializers/migrations/0200-video-published-at.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0205-user-nsfw-policy.ts b/server/initializers/migrations/0205-user-nsfw-policy.ts
index d0f6e8962..9c2786f12 100644
--- a/server/initializers/migrations/0205-user-nsfw-policy.ts
+++ b/server/initializers/migrations/0205-user-nsfw-policy.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0210-video-language.ts b/server/initializers/migrations/0210-video-language.ts
index ca95c7527..ee4ce9266 100644
--- a/server/initializers/migrations/0210-video-language.ts
+++ b/server/initializers/migrations/0210-video-language.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../constants' 2import { CONSTRAINTS_FIELDS } from '../constants'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 9
diff --git a/server/initializers/migrations/0215-video-support-length.ts b/server/initializers/migrations/0215-video-support-length.ts
index ba395050f..26c0ca700 100644
--- a/server/initializers/migrations/0215-video-support-length.ts
+++ b/server/initializers/migrations/0215-video-support-length.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 { 8 {
diff --git a/server/initializers/migrations/0255-video-blacklist-reason.ts b/server/initializers/migrations/0255-video-blacklist-reason.ts
index 69d6efb9e..7de982f93 100644
--- a/server/initializers/migrations/0255-video-blacklist-reason.ts
+++ b/server/initializers/migrations/0255-video-blacklist-reason.ts
@@ -1,5 +1,4 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { VideoAbuseState } from '../../../shared/models/videos'
3 2
4async function up (utils: { 3async function up (utils: {
5 transaction: Sequelize.Transaction 4 transaction: Sequelize.Transaction
diff --git a/server/initializers/migrations/0285-description-support.ts b/server/initializers/migrations/0285-description-support.ts
index 85ef4ef39..aab3a938f 100644
--- a/server/initializers/migrations/0285-description-support.ts
+++ b/server/initializers/migrations/0285-description-support.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0290-account-video-rate-url.ts b/server/initializers/migrations/0290-account-video-rate-url.ts
index bdabf2929..b974b1a81 100644
--- a/server/initializers/migrations/0290-account-video-rate-url.ts
+++ b/server/initializers/migrations/0290-account-video-rate-url.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0295-video-file-extname.ts b/server/initializers/migrations/0295-video-file-extname.ts
index dbf249f66..e1999b072 100644
--- a/server/initializers/migrations/0295-video-file-extname.ts
+++ b/server/initializers/migrations/0295-video-file-extname.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0300-user-videos-history-enabled.ts b/server/initializers/migrations/0300-user-videos-history-enabled.ts
index aa5fc21fb..5e35e14ba 100644
--- a/server/initializers/migrations/0300-user-videos-history-enabled.ts
+++ b/server/initializers/migrations/0300-user-videos-history-enabled.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0305-fix-unfederated-videos.ts b/server/initializers/migrations/0305-fix-unfederated-videos.ts
index be206601f..9c5d56b7b 100644
--- a/server/initializers/migrations/0305-fix-unfederated-videos.ts
+++ b/server/initializers/migrations/0305-fix-unfederated-videos.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0310-drop-unused-video-indexes.ts b/server/initializers/migrations/0310-drop-unused-video-indexes.ts
index d51f430c0..181858d3d 100644
--- a/server/initializers/migrations/0310-drop-unused-video-indexes.ts
+++ b/server/initializers/migrations/0310-drop-unused-video-indexes.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const indexNames = [ 9 const indexNames = [
diff --git a/server/initializers/migrations/0315-user-notifications.ts b/server/initializers/migrations/0315-user-notifications.ts
index 8284c58a0..0e3f4fbef 100644
--- a/server/initializers/migrations/0315-user-notifications.ts
+++ b/server/initializers/migrations/0315-user-notifications.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0320-blacklist-unfederate.ts b/server/initializers/migrations/0320-blacklist-unfederate.ts
index 6fb7bbb90..41de41c55 100644
--- a/server/initializers/migrations/0320-blacklist-unfederate.ts
+++ b/server/initializers/migrations/0320-blacklist-unfederate.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0325-video-abuse-fields.ts b/server/initializers/migrations/0325-video-abuse-fields.ts
index fca6d666f..d88724a20 100644
--- a/server/initializers/migrations/0325-video-abuse-fields.ts
+++ b/server/initializers/migrations/0325-video-abuse-fields.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0330-video-streaming-playlist.ts b/server/initializers/migrations/0330-video-streaming-playlist.ts
index c85a762ab..f75541a7f 100644
--- a/server/initializers/migrations/0330-video-streaming-playlist.ts
+++ b/server/initializers/migrations/0330-video-streaming-playlist.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0335-video-downloading-enabled.ts b/server/initializers/migrations/0335-video-downloading-enabled.ts
index e79466447..c745f1f02 100644
--- a/server/initializers/migrations/0335-video-downloading-enabled.ts
+++ b/server/initializers/migrations/0335-video-downloading-enabled.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import { Migration } from '../../models/migrations' 2import { Migration } from '../../models/migrations'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0340-add-originally-published-at.ts b/server/initializers/migrations/0340-add-originally-published-at.ts
index fe4f4a5f9..7cbc14ab5 100644
--- a/server/initializers/migrations/0340-add-originally-published-at.ts
+++ b/server/initializers/migrations/0340-add-originally-published-at.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0345-video-playlists.ts b/server/initializers/migrations/0345-video-playlists.ts
index de69f5b9e..76813f93f 100644
--- a/server/initializers/migrations/0345-video-playlists.ts
+++ b/server/initializers/migrations/0345-video-playlists.ts
@@ -4,8 +4,8 @@ import * as uuidv4 from 'uuid/v4'
4import { WEBSERVER } from '../constants' 4import { WEBSERVER } from '../constants'
5 5
6async function up (utils: { 6async function up (utils: {
7 transaction: Sequelize.Transaction, 7 transaction: Sequelize.Transaction
8 queryInterface: Sequelize.QueryInterface, 8 queryInterface: Sequelize.QueryInterface
9 sequelize: Sequelize.Sequelize 9 sequelize: Sequelize.Sequelize
10}): Promise<void> { 10}): Promise<void> {
11 const transaction = utils.transaction 11 const transaction = utils.transaction
diff --git a/server/initializers/migrations/0350-video-blacklist-type.ts b/server/initializers/migrations/0350-video-blacklist-type.ts
index 4849020ef..f79ae5ec7 100644
--- a/server/initializers/migrations/0350-video-blacklist-type.ts
+++ b/server/initializers/migrations/0350-video-blacklist-type.ts
@@ -2,9 +2,9 @@ import * as Sequelize from 'sequelize'
2import { VideoBlacklistType } from '../../../shared/models/videos' 2import { VideoBlacklistType } from '../../../shared/models/videos'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize, 7 sequelize: Sequelize.Sequelize
8 db: any 8 db: any
9}): Promise<void> { 9}): Promise<void> {
10 { 10 {
diff --git a/server/initializers/migrations/0355-p2p-peer-version.ts b/server/initializers/migrations/0355-p2p-peer-version.ts
index 18f23d9b7..89af28d07 100644
--- a/server/initializers/migrations/0355-p2p-peer-version.ts
+++ b/server/initializers/migrations/0355-p2p-peer-version.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 9
diff --git a/server/initializers/migrations/0360-notification-instance-follower.ts b/server/initializers/migrations/0360-notification-instance-follower.ts
index 05caf8e1d..6f9a01a9c 100644
--- a/server/initializers/migrations/0360-notification-instance-follower.ts
+++ b/server/initializers/migrations/0360-notification-instance-follower.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0365-user-admin-flags.ts b/server/initializers/migrations/0365-user-admin-flags.ts
index 20553100a..b705387da 100644
--- a/server/initializers/migrations/0365-user-admin-flags.ts
+++ b/server/initializers/migrations/0365-user-admin-flags.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0370-thumbnail.ts b/server/initializers/migrations/0370-thumbnail.ts
index 384ca1a15..07c25436a 100644
--- a/server/initializers/migrations/0370-thumbnail.ts
+++ b/server/initializers/migrations/0370-thumbnail.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0375-account-description.ts b/server/initializers/migrations/0375-account-description.ts
index 1258563fd..f9af942e0 100644
--- a/server/initializers/migrations/0375-account-description.ts
+++ b/server/initializers/migrations/0375-account-description.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0380-cleanup-timestamps.ts b/server/initializers/migrations/0380-cleanup-timestamps.ts
index 2a9fd6f02..18ecfb997 100644
--- a/server/initializers/migrations/0380-cleanup-timestamps.ts
+++ b/server/initializers/migrations/0380-cleanup-timestamps.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 try { 9 try {
diff --git a/server/initializers/migrations/0385-remove-actor-uuid.ts b/server/initializers/migrations/0385-remove-actor-uuid.ts
index 032c0562b..eefbc386b 100644
--- a/server/initializers/migrations/0385-remove-actor-uuid.ts
+++ b/server/initializers/migrations/0385-remove-actor-uuid.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 await utils.queryInterface.removeColumn('actor', 'uuid') 9 await utils.queryInterface.removeColumn('actor', 'uuid')
diff --git a/server/initializers/migrations/0390-user-pending-email.ts b/server/initializers/migrations/0390-user-pending-email.ts
index 5ca871746..9cf0affa5 100644
--- a/server/initializers/migrations/0390-user-pending-email.ts
+++ b/server/initializers/migrations/0390-user-pending-email.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0395-user-video-languages.ts b/server/initializers/migrations/0395-user-video-languages.ts
index 278698bf4..9c24fbc9a 100644
--- a/server/initializers/migrations/0395-user-video-languages.ts
+++ b/server/initializers/migrations/0395-user-video-languages.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0400-user-theme.ts b/server/initializers/migrations/0400-user-theme.ts
index f74d76115..7addb1bb3 100644
--- a/server/initializers/migrations/0400-user-theme.ts
+++ b/server/initializers/migrations/0400-user-theme.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0405-plugin.ts b/server/initializers/migrations/0405-plugin.ts
index c55b81960..5c290b986 100644
--- a/server/initializers/migrations/0405-plugin.ts
+++ b/server/initializers/migrations/0405-plugin.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0410-video-playlist-element.ts b/server/initializers/migrations/0410-video-playlist-element.ts
index f536632a2..1b4692357 100644
--- a/server/initializers/migrations/0410-video-playlist-element.ts
+++ b/server/initializers/migrations/0410-video-playlist-element.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0415-thumbnail-auto-generated.ts b/server/initializers/migrations/0415-thumbnail-auto-generated.ts
index f822a4c05..98d563c88 100644
--- a/server/initializers/migrations/0415-thumbnail-auto-generated.ts
+++ b/server/initializers/migrations/0415-thumbnail-auto-generated.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0420-avatar-lazy.ts b/server/initializers/migrations/0420-avatar-lazy.ts
index 5fc57aac2..5c74819d2 100644
--- a/server/initializers/migrations/0420-avatar-lazy.ts
+++ b/server/initializers/migrations/0420-avatar-lazy.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0425-nullable-actor-fields.ts b/server/initializers/migrations/0425-nullable-actor-fields.ts
index 4e5f9e6ab..720b99ccc 100644
--- a/server/initializers/migrations/0425-nullable-actor-fields.ts
+++ b/server/initializers/migrations/0425-nullable-actor-fields.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0430-auto-follow-notification-setting.ts b/server/initializers/migrations/0430-auto-follow-notification-setting.ts
index 034bdd46d..1734828a4 100644
--- a/server/initializers/migrations/0430-auto-follow-notification-setting.ts
+++ b/server/initializers/migrations/0430-auto-follow-notification-setting.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0435-user-modals.ts b/server/initializers/migrations/0435-user-modals.ts
index 5c2aa85b5..737440e9b 100644
--- a/server/initializers/migrations/0435-user-modals.ts
+++ b/server/initializers/migrations/0435-user-modals.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0440-user-auto-play-next-video.ts b/server/initializers/migrations/0440-user-auto-play-next-video.ts
index f0baafe7b..f3f663f59 100644
--- a/server/initializers/migrations/0440-user-auto-play-next-video.ts
+++ b/server/initializers/migrations/0440-user-auto-play-next-video.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0445-shared-inbox-optional.ts b/server/initializers/migrations/0445-shared-inbox-optional.ts
index dad2d6569..ade1a2a57 100644
--- a/server/initializers/migrations/0445-shared-inbox-optional.ts
+++ b/server/initializers/migrations/0445-shared-inbox-optional.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0450-streaming-playlist-files.ts b/server/initializers/migrations/0450-streaming-playlist-files.ts
index 460dac8be..08e2e3989 100644
--- a/server/initializers/migrations/0450-streaming-playlist-files.ts
+++ b/server/initializers/migrations/0450-streaming-playlist-files.ts
@@ -1,15 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { join } from 'path'
3import { HLS_STREAMING_PLAYLIST_DIRECTORY, WEBSERVER } from '@server/initializers/constants'
4import { CONFIG } from '@server/initializers/config'
5import { pathExists, stat, writeFile } from 'fs-extra'
6import * as parseTorrent from 'parse-torrent'
7import { createTorrentPromise } from '@server/helpers/webtorrent'
8 2
9async function up (utils: { 3async function up (utils: {
10 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
11 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
12 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
13 db: any 7 db: any
14}): Promise<void> { 8}): Promise<void> {
15 { 9 {
@@ -42,8 +36,8 @@ async function up (utils: {
42 { 36 {
43 const query = 'insert into "videoFile" ' + 37 const query = 'insert into "videoFile" ' +
44 '(resolution, size, "infoHash", "videoId", "createdAt", "updatedAt", fps, extname, "videoStreamingPlaylistId")' + 38 '(resolution, size, "infoHash", "videoId", "createdAt", "updatedAt", fps, extname, "videoStreamingPlaylistId")' +
45 '(SELECT "videoFile".resolution, "videoFile".size, \'fake\', NULL, "videoFile"."createdAt", "videoFile"."updatedAt", "videoFile"."fps", ' + 39 '(SELECT "videoFile".resolution, "videoFile".size, \'fake\', NULL, "videoFile"."createdAt", "videoFile"."updatedAt", ' +
46 '"videoFile".extname, "videoStreamingPlaylist".id FROM "videoStreamingPlaylist" ' + 40 '"videoFile"."fps", "videoFile".extname, "videoStreamingPlaylist".id FROM "videoStreamingPlaylist" ' +
47 'inner join video ON video.id = "videoStreamingPlaylist"."videoId" inner join "videoFile" ON "videoFile"."videoId" = video.id)' 41 'inner join video ON video.id = "videoStreamingPlaylist"."videoId" inner join "videoFile" ON "videoFile"."videoId" = video.id)'
48 42
49 await utils.sequelize.query(query, { transaction: utils.transaction }) 43 await utils.sequelize.query(query, { transaction: utils.transaction })
diff --git a/server/initializers/migrations/0455-soft-delete-video-comments.ts b/server/initializers/migrations/0455-soft-delete-video-comments.ts
index bcfb97b56..00e56015f 100644
--- a/server/initializers/migrations/0455-soft-delete-video-comments.ts
+++ b/server/initializers/migrations/0455-soft-delete-video-comments.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0460-user-playlist-autoplay.ts b/server/initializers/migrations/0460-user-playlist-autoplay.ts
index 3067ac1a4..d6f5081ab 100644
--- a/server/initializers/migrations/0460-user-playlist-autoplay.ts
+++ b/server/initializers/migrations/0460-user-playlist-autoplay.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0465-thumbnail-file-url-length.ts b/server/initializers/migrations/0465-thumbnail-file-url-length.ts
index db8c85c29..84a4fa0ba 100644
--- a/server/initializers/migrations/0465-thumbnail-file-url-length.ts
+++ b/server/initializers/migrations/0465-thumbnail-file-url-length.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0470-cleaup-indexes.ts b/server/initializers/migrations/0470-cleaup-indexes.ts
index 53e401c2b..7365c30f8 100644
--- a/server/initializers/migrations/0470-cleaup-indexes.ts
+++ b/server/initializers/migrations/0470-cleaup-indexes.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 await utils.sequelize.query('DROP INDEX IF EXISTS video_share_account_id;') 9 await utils.sequelize.query('DROP INDEX IF EXISTS video_share_account_id;')
diff --git a/server/initializers/migrations/0475-redundancy-expires-on.ts b/server/initializers/migrations/0475-redundancy-expires-on.ts
new file mode 100644
index 000000000..edbddba37
--- /dev/null
+++ b/server/initializers/migrations/0475-redundancy-expires-on.ts
@@ -0,0 +1,27 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize
7 db: any
8}): Promise<void> {
9 {
10 const data = {
11 type: Sequelize.DATE,
12 allowNull: true,
13 defaultValue: null
14 }
15
16 await utils.queryInterface.changeColumn('videoRedundancy', 'expiresOn', data)
17 }
18}
19
20function down (options) {
21 throw new Error('Not implemented.')
22}
23
24export {
25 up,
26 down
27}
diff --git a/server/initializers/migrations/0480-caption-file-url.ts b/server/initializers/migrations/0480-caption-file-url.ts
new file mode 100644
index 000000000..1f88206d3
--- /dev/null
+++ b/server/initializers/migrations/0480-caption-file-url.ts
@@ -0,0 +1,27 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize
7 db: any
8}): Promise<void> {
9 {
10 const data = {
11 type: Sequelize.STRING,
12 allowNull: true,
13 defaultValue: null
14 }
15
16 await utils.queryInterface.addColumn('videoCaption', 'fileUrl', data)
17 }
18}
19
20function down (options) {
21 throw new Error('Not implemented.')
22}
23
24export {
25 up,
26 down
27}
diff --git a/server/initializers/migrator.ts b/server/initializers/migrator.ts
index 1cb0116b7..77203ae24 100644
--- a/server/initializers/migrator.ts
+++ b/server/initializers/migrator.ts
@@ -20,7 +20,7 @@ async function migrate () {
20 } 20 }
21 21
22 const rows = await sequelizeTypescript.query<{ migrationVersion: number }>(query, options) 22 const rows = await sequelizeTypescript.query<{ migrationVersion: number }>(query, options)
23 if (rows && rows[0] && rows[0].migrationVersion) { 23 if (rows?.[0]?.migrationVersion) {
24 actualVersion = rows[0].migrationVersion 24 actualVersion = rows[0].migrationVersion
25 } 25 }
26 26
@@ -60,7 +60,7 @@ export {
60async function getMigrationScripts () { 60async function getMigrationScripts () {
61 const files = await readdir(path.join(__dirname, 'migrations')) 61 const files = await readdir(path.join(__dirname, 'migrations'))
62 const filesToMigrate: { 62 const filesToMigrate: {
63 version: string, 63 version: string
64 script: string 64 script: string
65 }[] = [] 65 }[] = []
66 66
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index f802658cf..3f6edc070 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -1,6 +1,6 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { Transaction } from 'sequelize' 2import { Transaction } from 'sequelize'
3import * as url from 'url' 3import { URL } from 'url'
4import * as uuidv4 from 'uuid/v4' 4import * as uuidv4 from 'uuid/v4'
5import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub' 5import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub'
6import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' 6import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects'
@@ -33,8 +33,7 @@ import {
33 MActorFull, 33 MActorFull,
34 MActorFullActor, 34 MActorFullActor,
35 MActorId, 35 MActorId,
36 MChannel, 36 MChannel
37 MChannelAccountDefault
38} from '../../typings/models' 37} from '../../typings/models'
39 38
40// Set account keys, this could be long so process after the account creation and do not block the client 39// Set account keys, this could be long so process after the account creation and do not block the client
@@ -121,13 +120,13 @@ async function getOrCreateActorAndServerAndModel (
121 120
122 if ((created === true || refreshed === true) && updateCollections === true) { 121 if ((created === true || refreshed === true) && updateCollections === true) {
123 const payload = { uri: actor.outboxUrl, type: 'activity' as 'activity' } 122 const payload = { uri: actor.outboxUrl, type: 'activity' as 'activity' }
124 await JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) 123 await JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload })
125 } 124 }
126 125
127 // We created a new account: fetch the playlists 126 // We created a new account: fetch the playlists
128 if (created === true && actor.Account && accountPlaylistsUrl) { 127 if (created === true && actor.Account && accountPlaylistsUrl) {
129 const payload = { uri: accountPlaylistsUrl, accountId: actor.Account.id, type: 'account-playlists' as 'account-playlists' } 128 const payload = { uri: accountPlaylistsUrl, accountId: actor.Account.id, type: 'account-playlists' as 'account-playlists' }
130 await JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) 129 await JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload })
131 } 130 }
132 131
133 return actorRefreshed 132 return actorRefreshed
@@ -215,7 +214,7 @@ async function fetchActorTotalItems (url: string) {
215 } 214 }
216} 215}
217 216
218async function getAvatarInfoIfExists (actorJSON: ActivityPubActor) { 217function getAvatarInfoIfExists (actorJSON: ActivityPubActor) {
219 if ( 218 if (
220 actorJSON.icon && actorJSON.icon.type === 'Image' && MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined && 219 actorJSON.icon && actorJSON.icon.type === 'Image' && MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
221 isActivityPubUrlValid(actorJSON.icon.url) 220 isActivityPubUrlValid(actorJSON.icon.url)
@@ -271,7 +270,10 @@ async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannel
271 270
272 if (statusCode === 404) { 271 if (statusCode === 404) {
273 logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url) 272 logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url)
274 actor.Account ? actor.Account.destroy() : actor.VideoChannel.destroy() 273 actor.Account
274 ? await actor.Account.destroy()
275 : await actor.VideoChannel.destroy()
276
275 return { actor: undefined, refreshed: false } 277 return { actor: undefined, refreshed: false }
276 } 278 }
277 279
@@ -337,14 +339,14 @@ function saveActorAndServerAndModelIfNotExist (
337 ownerActor?: MActorFullActor, 339 ownerActor?: MActorFullActor,
338 t?: Transaction 340 t?: Transaction
339): Bluebird<MActorFullActor> | Promise<MActorFullActor> { 341): Bluebird<MActorFullActor> | Promise<MActorFullActor> {
340 let actor = result.actor 342 const actor = result.actor
341 343
342 if (t !== undefined) return save(t) 344 if (t !== undefined) return save(t)
343 345
344 return sequelizeTypescript.transaction(t => save(t)) 346 return sequelizeTypescript.transaction(t => save(t))
345 347
346 async function save (t: Transaction) { 348 async function save (t: Transaction) {
347 const actorHost = url.parse(actor.url).host 349 const actorHost = new URL(actor.url).host
348 350
349 const serverOptions = { 351 const serverOptions = {
350 where: { 352 where: {
@@ -402,7 +404,7 @@ type FetchRemoteActorResult = {
402 support?: string 404 support?: string
403 playlists?: string 405 playlists?: string
404 avatar?: { 406 avatar?: {
405 name: string, 407 name: string
406 fileUrl: string 408 fileUrl: string
407 } 409 }
408 attributedTo: ActivityPubAttributedTo[] 410 attributedTo: ActivityPubAttributedTo[]
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts
index f2ab54cf7..9933ae2b5 100644
--- a/server/lib/activitypub/audience.ts
+++ b/server/lib/activitypub/audience.ts
@@ -4,11 +4,11 @@ import { ACTIVITY_PUB } from '../../initializers/constants'
4import { ActorModel } from '../../models/activitypub/actor' 4import { ActorModel } from '../../models/activitypub/actor'
5import { VideoModel } from '../../models/video/video' 5import { VideoModel } from '../../models/video/video'
6import { VideoShareModel } from '../../models/video/video-share' 6import { VideoShareModel } from '../../models/video/video-share'
7import { MActorFollowersUrl, MActorLight, MCommentOwner, MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../../typings/models' 7import { MActorFollowersUrl, MActorLight, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '../../typings/models'
8 8
9function getRemoteVideoAudience (video: MVideoAccountLight, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience { 9function getRemoteVideoAudience (accountActor: MActorUrl, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience {
10 return { 10 return {
11 to: [ video.VideoChannel.Account.Actor.url ], 11 to: [ accountActor.url ],
12 cc: actorsInvolvedInVideo.map(a => a.followersUrl) 12 cc: actorsInvolvedInVideo.map(a => a.followersUrl)
13 } 13 }
14} 14}
@@ -48,7 +48,7 @@ function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[
48 } 48 }
49} 49}
50 50
51async function getActorsInvolvedInVideo (video: MVideo, t: Transaction) { 51async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) {
52 const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t) 52 const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t)
53 53
54 const videoAll = video as VideoModel 54 const videoAll = video as VideoModel
diff --git a/server/lib/activitypub/cache-file.ts b/server/lib/activitypub/cache-file.ts
index 65b2dcb49..8252e95e9 100644
--- a/server/lib/activitypub/cache-file.ts
+++ b/server/lib/activitypub/cache-file.ts
@@ -13,7 +13,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject
13 if (!playlist) throw new Error('Cannot find HLS playlist of video ' + video.url) 13 if (!playlist) throw new Error('Cannot find HLS playlist of video ' + video.url)
14 14
15 return { 15 return {
16 expiresOn: new Date(cacheFileObject.expires), 16 expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null,
17 url: cacheFileObject.id, 17 url: cacheFileObject.id,
18 fileUrl: url.href, 18 fileUrl: url.href,
19 strategy: null, 19 strategy: null,
@@ -30,7 +30,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject
30 if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${url.fps} of video ${video.url}`) 30 if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${url.fps} of video ${video.url}`)
31 31
32 return { 32 return {
33 expiresOn: new Date(cacheFileObject.expires), 33 expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null,
34 url: cacheFileObject.id, 34 url: cacheFileObject.id,
35 fileUrl: url.href, 35 fileUrl: url.href,
36 strategy: null, 36 strategy: null,
diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts
index 9e469e3e6..eeafdf4ba 100644
--- a/server/lib/activitypub/crawl.ts
+++ b/server/lib/activitypub/crawl.ts
@@ -3,7 +3,7 @@ import { doRequest } from '../../helpers/requests'
3import { logger } from '../../helpers/logger' 3import { logger } from '../../helpers/logger'
4import * as Bluebird from 'bluebird' 4import * as Bluebird from 'bluebird'
5import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' 5import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub'
6import { parse } from 'url' 6import { URL } from 'url'
7 7
8type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>) 8type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>)
9type CleanerFunction = (startedDate: Date) => (Promise<any> | Bluebird<any>) 9type CleanerFunction = (startedDate: Date) => (Promise<any> | Bluebird<any>)
@@ -24,7 +24,7 @@ async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T>
24 const response = await doRequest<ActivityPubOrderedCollection<T>>(options) 24 const response = await doRequest<ActivityPubOrderedCollection<T>>(options)
25 const firstBody = response.body 25 const firstBody = response.body
26 26
27 let limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT 27 const limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT
28 let i = 0 28 let i = 0
29 let nextLink = firstBody.first 29 let nextLink = firstBody.first
30 while (nextLink && i < limit) { 30 while (nextLink && i < limit) {
@@ -32,7 +32,7 @@ async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T>
32 32
33 if (typeof nextLink === 'string') { 33 if (typeof nextLink === 'string') {
34 // Don't crawl ourselves 34 // Don't crawl ourselves
35 const remoteHost = parse(nextLink).host 35 const remoteHost = new URL(nextLink).host
36 if (remoteHost === WEBSERVER.HOST) continue 36 if (remoteHost === WEBSERVER.HOST) continue
37 37
38 options.uri = nextLink 38 options.uri = nextLink
diff --git a/server/lib/activitypub/follow.ts b/server/lib/activitypub/follow.ts
index 1abf43cd4..a1c95504e 100644
--- a/server/lib/activitypub/follow.ts
+++ b/server/lib/activitypub/follow.ts
@@ -27,7 +27,6 @@ async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors) {
27 } 27 }
28 28
29 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) 29 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
30 .catch(err => logger.error('Cannot create auto follow back job for %s.', host, err))
31 } 30 }
32} 31}
33 32
diff --git a/server/lib/activitypub/process/process-view.ts b/server/lib/activitypub/process/process-view.ts
index df29ee968..b3b6c933d 100644
--- a/server/lib/activitypub/process/process-view.ts
+++ b/server/lib/activitypub/process/process-view.ts
@@ -23,7 +23,8 @@ async function processCreateView (activity: ActivityView | ActivityCreate, byAct
23 23
24 const options = { 24 const options = {
25 videoObject, 25 videoObject,
26 fetchType: 'only-video' as 'only-video' 26 fetchType: 'only-immutable-attributes' as 'only-immutable-attributes',
27 allowRefresh: false as false
27 } 28 }
28 const { video } = await getOrCreateVideoAndAccountAndChannel(options) 29 const { video } = await getOrCreateVideoAndAccountAndChannel(options)
29 30
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts
index 9f0225b64..c4c6b849b 100644
--- a/server/lib/activitypub/send/send-accept.ts
+++ b/server/lib/activitypub/send/send-accept.ts
@@ -5,7 +5,7 @@ import { buildFollowActivity } from './send-follow'
5import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6import { MActor, MActorFollowActors } from '../../../typings/models' 6import { MActor, MActorFollowActors } from '../../../typings/models'
7 7
8async function sendAccept (actorFollow: MActorFollowActors) { 8function sendAccept (actorFollow: MActorFollowActors) {
9 const follower = actorFollow.ActorFollower 9 const follower = actorFollow.ActorFollower
10 const me = actorFollow.ActorFollowing 10 const me = actorFollow.ActorFollowing
11 11
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
index a0f33852c..d03b358f1 100644
--- a/server/lib/activitypub/send/send-announce.ts
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -28,7 +28,7 @@ async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare,
28 logger.info('Creating job to send announce %s.', videoShare.url) 28 logger.info('Creating job to send announce %s.', videoShare.url)
29 29
30 const followersException = [ byActor ] 30 const followersException = [ byActor ]
31 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException) 31 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException, 'Announce')
32} 32}
33 33
34function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce { 34function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce {
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index 1709d8348..8bdcf6417 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -16,6 +16,7 @@ import {
16 MVideoRedundancyFileVideo, 16 MVideoRedundancyFileVideo,
17 MVideoRedundancyStreamingPlaylistVideo 17 MVideoRedundancyStreamingPlaylistVideo
18} from '../../../typings/models' 18} from '../../../typings/models'
19import { ContextType } from '@server/helpers/activitypub'
19 20
20async function sendCreateVideo (video: MVideoAP, t: Transaction) { 21async function sendCreateVideo (video: MVideoAP, t: Transaction) {
21 if (!video.hasPrivacyForFederation()) return undefined 22 if (!video.hasPrivacyForFederation()) return undefined
@@ -42,7 +43,8 @@ async function sendCreateCacheFile (
42 byActor, 43 byActor,
43 video, 44 video,
44 url: fileRedundancy.url, 45 url: fileRedundancy.url,
45 object: fileRedundancy.toActivityPubObject() 46 object: fileRedundancy.toActivityPubObject(),
47 contextType: 'CacheFile'
46 }) 48 })
47} 49}
48 50
@@ -130,11 +132,12 @@ export {
130// --------------------------------------------------------------------------- 132// ---------------------------------------------------------------------------
131 133
132async function sendVideoRelatedCreateActivity (options: { 134async function sendVideoRelatedCreateActivity (options: {
133 byActor: MActorLight, 135 byActor: MActorLight
134 video: MVideoAccountLight, 136 video: MVideoAccountLight
135 url: string, 137 url: string
136 object: any, 138 object: any
137 transaction?: Transaction 139 transaction?: Transaction
140 contextType?: ContextType
138}) { 141}) {
139 const activityBuilder = (audience: ActivityAudience) => { 142 const activityBuilder = (audience: ActivityAudience) => {
140 return buildCreateActivity(options.url, options.byActor, options.object, audience) 143 return buildCreateActivity(options.url, options.byActor, options.object, audience)
diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts
index 6e41f241f..600469c71 100644
--- a/server/lib/activitypub/send/send-dislike.ts
+++ b/server/lib/activitypub/send/send-dislike.ts
@@ -6,7 +6,7 @@ import { sendVideoRelatedActivity } from './utils'
6import { audiencify, getAudience } from '../audience' 6import { audiencify, getAudience } from '../audience'
7import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' 7import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models'
8 8
9async function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { 9function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
10 logger.info('Creating job to dislike %s.', video.url) 10 logger.info('Creating job to dislike %s.', video.url)
11 11
12 const activityBuilder = (audience: ActivityAudience) => { 12 const activityBuilder = (audience: ActivityAudience) => {
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts
index da7638a7b..e4e523631 100644
--- a/server/lib/activitypub/send/send-flag.ts
+++ b/server/lib/activitypub/send/send-flag.ts
@@ -7,7 +7,7 @@ import { Transaction } from 'sequelize'
7import { MActor, MVideoFullLight } from '../../../typings/models' 7import { MActor, MVideoFullLight } from '../../../typings/models'
8import { MVideoAbuseVideo } from '../../../typings/models/video' 8import { MVideoAbuseVideo } from '../../../typings/models/video'
9 9
10async function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) { 10function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) {
11 if (!video.VideoChannel.Account.Actor.serverId) return // Local user 11 if (!video.VideoChannel.Account.Actor.serverId) return // Local user
12 12
13 const url = getVideoAbuseActivityPubUrl(videoAbuse) 13 const url = getVideoAbuseActivityPubUrl(videoAbuse)
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index e84a6f98b..5db252325 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -6,7 +6,7 @@ import { audiencify, getAudience } from '../audience'
6import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
7import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' 7import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models'
8 8
9async function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { 9function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
10 logger.info('Creating job to like %s.', video.url) 10 logger.info('Creating job to like %s.', video.url)
11 11
12 const activityBuilder = (audience: ActivityAudience) => { 12 const activityBuilder = (audience: ActivityAudience) => {
diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts
index 4258a3c36..643c468a9 100644
--- a/server/lib/activitypub/send/send-reject.ts
+++ b/server/lib/activitypub/send/send-reject.ts
@@ -5,7 +5,7 @@ import { buildFollowActivity } from './send-follow'
5import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6import { MActor } from '../../../typings/models' 6import { MActor } from '../../../typings/models'
7 7
8async function sendReject (follower: MActor, following: MActor) { 8function sendReject (follower: MActor, following: MActor) {
9 if (!follower.serverId) { // This should never happen 9 if (!follower.serverId) { // This should never happen
10 logger.warn('Do not sending reject to local follower.') 10 logger.warn('Do not sending reject to local follower.')
11 return 11 return
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index e9ab5b3c5..33f1d4921 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -28,7 +28,7 @@ import {
28 MVideoShare 28 MVideoShare
29} from '../../../typings/models' 29} from '../../../typings/models'
30 30
31async function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) { 31function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) {
32 const me = actorFollow.ActorFollower 32 const me = actorFollow.ActorFollower
33 const following = actorFollow.ActorFollowing 33 const following = actorFollow.ActorFollowing
34 34
@@ -118,10 +118,10 @@ function undoActivityData (
118} 118}
119 119
120async function sendUndoVideoRelatedActivity (options: { 120async function sendUndoVideoRelatedActivity (options: {
121 byActor: MActor, 121 byActor: MActor
122 video: MVideoAccountLight, 122 video: MVideoAccountLight
123 url: string, 123 url: string
124 activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, 124 activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce
125 transaction: Transaction 125 transaction: Transaction
126}) { 126}) {
127 const activityBuilder = (audience: ActivityAudience) => { 127 const activityBuilder = (audience: ActivityAudience) => {
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts
index 9c76671b5..2b01ca5e7 100644
--- a/server/lib/activitypub/send/send-update.ts
+++ b/server/lib/activitypub/send/send-update.ts
@@ -8,7 +8,6 @@ import { getUpdateActivityPubUrl } from '../url'
8import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' 8import { broadcastToFollowers, sendVideoRelatedActivity } from './utils'
9import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' 9import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience'
10import { logger } from '../../../helpers/logger' 10import { logger } from '../../../helpers/logger'
11import { VideoCaptionModel } from '../../../models/video/video-caption'
12import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' 11import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
13import { getServerActor } from '../../../helpers/utils' 12import { getServerActor } from '../../../helpers/utils'
14import { 13import {
@@ -29,7 +28,7 @@ async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction
29 28
30 logger.info('Creating job to update video %s.', video.url) 29 logger.info('Creating job to update video %s.', video.url)
31 30
32 const byActor = overrodeByActor ? overrodeByActor : video.VideoChannel.Account.Actor 31 const byActor = overrodeByActor || video.VideoChannel.Account.Actor
33 32
34 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) 33 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString())
35 34
@@ -85,7 +84,7 @@ async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVide
85 return buildUpdateActivity(url, byActor, redundancyObject, audience) 84 return buildUpdateActivity(url, byActor, redundancyObject, audience)
86 } 85 }
87 86
88 return sendVideoRelatedActivity(activityBuilder, { byActor, video }) 87 return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' })
89} 88}
90 89
91async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { 90async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) {
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts
index 8809417f9..1f864ea52 100644
--- a/server/lib/activitypub/send/send-view.ts
+++ b/server/lib/activitypub/send/send-view.ts
@@ -5,9 +5,9 @@ import { getVideoLikeActivityPubUrl } from '../url'
5import { sendVideoRelatedActivity } from './utils' 5import { sendVideoRelatedActivity } from './utils'
6import { audiencify, getAudience } from '../audience' 6import { audiencify, getAudience } from '../audience'
7import { logger } from '../../../helpers/logger' 7import { logger } from '../../../helpers/logger'
8import { MActorAudience, MVideoAccountLight, MVideoUrl } from '@server/typings/models' 8import { MActorAudience, MVideoImmutable, MVideoUrl } from '@server/typings/models'
9 9
10async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Transaction) { 10async function sendView (byActor: ActorModel, video: MVideoImmutable, t: Transaction) {
11 logger.info('Creating job to send view of %s.', video.url) 11 logger.info('Creating job to send view of %s.', video.url)
12 12
13 const activityBuilder = (audience: ActivityAudience) => { 13 const activityBuilder = (audience: ActivityAudience) => {
@@ -16,7 +16,7 @@ async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Tran
16 return buildViewActivity(url, byActor, video, audience) 16 return buildViewActivity(url, byActor, video, audience)
17 } 17 }
18 18
19 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) 19 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t, contextType: 'View' })
20} 20}
21 21
22function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView { 22function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView {
diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts
index 77b723479..b57bae8fd 100644
--- a/server/lib/activitypub/send/utils.ts
+++ b/server/lib/activitypub/send/utils.ts
@@ -7,24 +7,28 @@ import { JobQueue } from '../../job-queue'
7import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' 7import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience'
8import { getServerActor } from '../../../helpers/utils' 8import { getServerActor } from '../../../helpers/utils'
9import { afterCommitIfTransaction } from '../../../helpers/database-utils' 9import { afterCommitIfTransaction } from '../../../helpers/database-utils'
10import { MActorWithInboxes, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models' 10import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../typings/models'
11import { ContextType } from '@server/helpers/activitypub'
11 12
12async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { 13async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
13 byActor: MActorLight, 14 byActor: MActorLight
14 video: MVideoAccountLight, 15 video: MVideoImmutable | MVideoAccountLight
15 transaction?: Transaction 16 transaction?: Transaction
17 contextType?: ContextType
16}) { 18}) {
17 const { byActor, video, transaction } = options 19 const { byActor, video, transaction, contextType } = options
18 20
19 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction) 21 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction)
20 22
21 // Send to origin 23 // Send to origin
22 if (video.isOwned() === false) { 24 if (video.isOwned() === false) {
23 const audience = getRemoteVideoAudience(video, actorsInvolvedInVideo) 25 const accountActor = (video as MVideoAccountLight).VideoChannel?.Account?.Actor || await ActorModel.loadAccountActorByVideoId(video.id)
26
27 const audience = getRemoteVideoAudience(accountActor, actorsInvolvedInVideo)
24 const activity = activityBuilder(audience) 28 const activity = activityBuilder(audience)
25 29
26 return afterCommitIfTransaction(transaction, () => { 30 return afterCommitIfTransaction(transaction, () => {
27 return unicastTo(activity, byActor, video.VideoChannel.Account.Actor.getSharedInbox()) 31 return unicastTo(activity, byActor, accountActor.getSharedInbox(), contextType)
28 }) 32 })
29 } 33 }
30 34
@@ -34,14 +38,14 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
34 38
35 const actorsException = [ byActor ] 39 const actorsException = [ byActor ]
36 40
37 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException) 41 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException, contextType)
38} 42}
39 43
40async function forwardVideoRelatedActivity ( 44async function forwardVideoRelatedActivity (
41 activity: Activity, 45 activity: Activity,
42 t: Transaction, 46 t: Transaction,
43 followersException: MActorWithInboxes[] = [], 47 followersException: MActorWithInboxes[] = [],
44 video: MVideo 48 video: MVideoId
45) { 49) {
46 // Mastodon does not add our announces in audience, so we forward to them manually 50 // Mastodon does not add our announces in audience, so we forward to them manually
47 const additionalActors = await getActorsInvolvedInVideo(video, t) 51 const additionalActors = await getActorsInvolvedInVideo(video, t)
@@ -90,11 +94,12 @@ async function broadcastToFollowers (
90 byActor: MActorId, 94 byActor: MActorId,
91 toFollowersOf: MActorId[], 95 toFollowersOf: MActorId[],
92 t: Transaction, 96 t: Transaction,
93 actorsException: MActorWithInboxes[] = [] 97 actorsException: MActorWithInboxes[] = [],
98 contextType?: ContextType
94) { 99) {
95 const uris = await computeFollowerUris(toFollowersOf, actorsException, t) 100 const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
96 101
97 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) 102 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType))
98} 103}
99 104
100async function broadcastToActors ( 105async function broadcastToActors (
@@ -102,13 +107,14 @@ async function broadcastToActors (
102 byActor: MActorId, 107 byActor: MActorId,
103 toActors: MActor[], 108 toActors: MActor[],
104 t?: Transaction, 109 t?: Transaction,
105 actorsException: MActorWithInboxes[] = [] 110 actorsException: MActorWithInboxes[] = [],
111 contextType?: ContextType
106) { 112) {
107 const uris = await computeUris(toActors, actorsException) 113 const uris = await computeUris(toActors, actorsException)
108 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) 114 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType))
109} 115}
110 116
111function broadcastTo (uris: string[], data: any, byActor: MActorId) { 117function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?: ContextType) {
112 if (uris.length === 0) return undefined 118 if (uris.length === 0) return undefined
113 119
114 logger.debug('Creating broadcast job.', { uris }) 120 logger.debug('Creating broadcast job.', { uris })
@@ -116,19 +122,21 @@ function broadcastTo (uris: string[], data: any, byActor: MActorId) {
116 const payload = { 122 const payload = {
117 uris, 123 uris,
118 signatureActorId: byActor.id, 124 signatureActorId: byActor.id,
119 body: data 125 body: data,
126 contextType
120 } 127 }
121 128
122 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) 129 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
123} 130}
124 131
125function unicastTo (data: any, byActor: MActorId, toActorUrl: string) { 132function unicastTo (data: any, byActor: MActorId, toActorUrl: string, contextType?: ContextType) {
126 logger.debug('Creating unicast job.', { uri: toActorUrl }) 133 logger.debug('Creating unicast job.', { uri: toActorUrl })
127 134
128 const payload = { 135 const payload = {
129 uri: toActorUrl, 136 uri: toActorUrl,
130 signatureActorId: byActor.id, 137 signatureActorId: byActor.id,
131 body: data 138 body: data,
139 contextType
132 } 140 }
133 141
134 JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload }) 142 JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload })
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts
index d5c078a29..8642d2432 100644
--- a/server/lib/activitypub/video-comments.ts
+++ b/server/lib/activitypub/video-comments.ts
@@ -10,9 +10,9 @@ import { checkUrlsSameHost } from '../../helpers/activitypub'
10import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video' 10import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video'
11 11
12type ResolveThreadParams = { 12type ResolveThreadParams = {
13 url: string, 13 url: string
14 comments?: MCommentOwner[], 14 comments?: MCommentOwner[]
15 isVideo?: boolean, 15 isVideo?: boolean
16 commentCreated?: boolean 16 commentCreated?: boolean
17} 17}
18type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }> 18type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }>
@@ -28,7 +28,7 @@ async function resolveThread (params: ResolveThreadParams): ResolveThreadResult
28 if (params.commentCreated === undefined) params.commentCreated = false 28 if (params.commentCreated === undefined) params.commentCreated = false
29 if (params.comments === undefined) params.comments = [] 29 if (params.comments === undefined) params.comments = []
30 30
31 // Already have this comment? 31 // Already have this comment?
32 if (isVideo !== true) { 32 if (isVideo !== true) {
33 const result = await resolveCommentFromDB(params) 33 const result = await resolveCommentFromDB(params)
34 if (result) return result 34 if (result) return result
@@ -87,7 +87,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
87 87
88 let resultComment: MCommentOwnerVideo 88 let resultComment: MCommentOwnerVideo
89 if (comments.length !== 0) { 89 if (comments.length !== 0) {
90 const firstReply = comments[ comments.length - 1 ] as MCommentOwnerVideo 90 const firstReply = comments[comments.length - 1] as MCommentOwnerVideo
91 firstReply.inReplyToCommentId = null 91 firstReply.inReplyToCommentId = null
92 firstReply.originCommentId = null 92 firstReply.originCommentId = null
93 firstReply.videoId = video.id 93 firstReply.videoId = video.id
@@ -97,9 +97,9 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
97 comments[comments.length - 1] = await firstReply.save() 97 comments[comments.length - 1] = await firstReply.save()
98 98
99 for (let i = comments.length - 2; i >= 0; i--) { 99 for (let i = comments.length - 2; i >= 0; i--) {
100 const comment = comments[ i ] as MCommentOwnerVideo 100 const comment = comments[i] as MCommentOwnerVideo
101 comment.originCommentId = firstReply.id 101 comment.originCommentId = firstReply.id
102 comment.inReplyToCommentId = comments[ i + 1 ].id 102 comment.inReplyToCommentId = comments[i + 1].id
103 comment.videoId = video.id 103 comment.videoId = video.id
104 comment.changed('updatedAt', true) 104 comment.changed('updatedAt', true)
105 comment.Video = video 105 comment.Video = video
diff --git a/server/lib/activitypub/video-rates.ts b/server/lib/activitypub/video-rates.ts
index 6bd46bb58..79ccfbc7e 100644
--- a/server/lib/activitypub/video-rates.ts
+++ b/server/lib/activitypub/video-rates.ts
@@ -58,8 +58,6 @@ async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateTy
58 const field = rate === 'like' ? 'likes' : 'dislikes' 58 const field = rate === 'like' ? 'likes' : 'dislikes'
59 await video.increment(field, { by: rateCounts }) 59 await video.increment(field, { by: rateCounts })
60 } 60 }
61
62 return
63} 61}
64 62
65async function sendVideoRateChange ( 63async function sendVideoRateChange (
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index ade93150f..7d8296e45 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -6,7 +6,8 @@ import {
6 ActivityHashTagObject, 6 ActivityHashTagObject,
7 ActivityMagnetUrlObject, 7 ActivityMagnetUrlObject,
8 ActivityPlaylistSegmentHashesObject, 8 ActivityPlaylistSegmentHashesObject,
9 ActivityPlaylistUrlObject, ActivityTagObject, 9 ActivityPlaylistUrlObject,
10 ActivityTagObject,
10 ActivityUrlObject, 11 ActivityUrlObject,
11 ActivityVideoUrlObject, 12 ActivityVideoUrlObject,
12 VideoState 13 VideoState
@@ -17,14 +18,14 @@ import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validat
17import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' 18import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
18import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' 19import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils'
19import { logger } from '../../helpers/logger' 20import { logger } from '../../helpers/logger'
20import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' 21import { doRequest } from '../../helpers/requests'
21import { 22import {
22 ACTIVITY_PUB, 23 ACTIVITY_PUB,
23 MIMETYPES, 24 MIMETYPES,
24 P2P_MEDIA_LOADER_PEER_VERSION, 25 P2P_MEDIA_LOADER_PEER_VERSION,
25 PREVIEWS_SIZE, 26 PREVIEWS_SIZE,
26 REMOTE_SCHEME, 27 REMOTE_SCHEME,
27 STATIC_PATHS 28 STATIC_PATHS, THUMBNAILS_SIZE
28} from '../../initializers/constants' 29} from '../../initializers/constants'
29import { TagModel } from '../../models/video/tag' 30import { TagModel } from '../../models/video/tag'
30import { VideoModel } from '../../models/video/video' 31import { VideoModel } from '../../models/video/video'
@@ -40,7 +41,7 @@ import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub
40import { createRates } from './video-rates' 41import { createRates } from './video-rates'
41import { addVideoShares, shareVideoByServerAndChannel } from './share' 42import { addVideoShares, shareVideoByServerAndChannel } from './share'
42import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' 43import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video'
43import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' 44import { buildRemoteVideoBaseUrl, checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
44import { Notifier } from '../notifier' 45import { Notifier } from '../notifier'
45import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' 46import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
46import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 47import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
@@ -67,10 +68,11 @@ import {
67 MVideoAPWithoutCaption, 68 MVideoAPWithoutCaption,
68 MVideoFile, 69 MVideoFile,
69 MVideoFullLight, 70 MVideoFullLight,
70 MVideoId, 71 MVideoId, MVideoImmutable,
71 MVideoThumbnail 72 MVideoThumbnail
72} from '../../typings/models' 73} from '../../typings/models'
73import { MThumbnail } from '../../typings/models/video/thumbnail' 74import { MThumbnail } from '../../typings/models/video/thumbnail'
75import { maxBy, minBy } from 'lodash'
74 76
75async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) { 77async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) {
76 const video = videoArg as MVideoAP 78 const video = videoArg as MVideoAP
@@ -131,19 +133,6 @@ async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
131 return body.description ? body.description : '' 133 return body.description ? body.description : ''
132} 134}
133 135
134function fetchRemoteVideoStaticFile (video: MVideoAccountLight, path: string, destPath: string) {
135 const url = buildRemoteBaseUrl(video, path)
136
137 // We need to provide a callback, if no we could have an uncaught exception
138 return doRequestAndSaveToFile({ uri: url }, destPath)
139}
140
141function buildRemoteBaseUrl (video: MVideoAccountLight, path: string) {
142 const host = video.VideoChannel.Account.Actor.Server.host
143
144 return REMOTE_SCHEME.HTTP + '://' + host + path
145}
146
147function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) { 136function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) {
148 const channel = videoObject.attributedTo.find(a => a.type === 'Group') 137 const channel = videoObject.attributedTo.find(a => a.type === 'Group')
149 if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url) 138 if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url)
@@ -173,7 +162,7 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo
173 const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'like' as 'like', crawlStartDate) 162 const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'like' as 'like', crawlStartDate)
174 163
175 await crawlCollectionPage<string>(fetchedVideo.likes, handler, cleaner) 164 await crawlCollectionPage<string>(fetchedVideo.likes, handler, cleaner)
176 .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err })) 165 .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err, rootUrl: fetchedVideo.likes }))
177 } else { 166 } else {
178 jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' }) 167 jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' })
179 } 168 }
@@ -183,7 +172,7 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo
183 const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'dislike' as 'dislike', crawlStartDate) 172 const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'dislike' as 'dislike', crawlStartDate)
184 173
185 await crawlCollectionPage<string>(fetchedVideo.dislikes, handler, cleaner) 174 await crawlCollectionPage<string>(fetchedVideo.dislikes, handler, cleaner)
186 .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err })) 175 .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err, rootUrl: fetchedVideo.dislikes }))
187 } else { 176 } else {
188 jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' }) 177 jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' })
189 } 178 }
@@ -193,7 +182,7 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo
193 const cleaner = crawlStartDate => VideoShareModel.cleanOldSharesOf(video.id, crawlStartDate) 182 const cleaner = crawlStartDate => VideoShareModel.cleanOldSharesOf(video.id, crawlStartDate)
194 183
195 await crawlCollectionPage<string>(fetchedVideo.shares, handler, cleaner) 184 await crawlCollectionPage<string>(fetchedVideo.shares, handler, cleaner)
196 .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err })) 185 .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err, rootUrl: fetchedVideo.shares }))
197 } else { 186 } else {
198 jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) 187 jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' })
199 } 188 }
@@ -203,32 +192,49 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo
203 const cleaner = crawlStartDate => VideoCommentModel.cleanOldCommentsOf(video.id, crawlStartDate) 192 const cleaner = crawlStartDate => VideoCommentModel.cleanOldCommentsOf(video.id, crawlStartDate)
204 193
205 await crawlCollectionPage<string>(fetchedVideo.comments, handler, cleaner) 194 await crawlCollectionPage<string>(fetchedVideo.comments, handler, cleaner)
206 .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err })) 195 .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err, rootUrl: fetchedVideo.comments }))
207 } else { 196 } else {
208 jobPayloads.push({ uri: fetchedVideo.comments, videoId: video.id, type: 'video-comments' as 'video-comments' }) 197 jobPayloads.push({ uri: fetchedVideo.comments, videoId: video.id, type: 'video-comments' as 'video-comments' })
209 } 198 }
210 199
211 await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) 200 await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload }))
212} 201}
213 202
214function getOrCreateVideoAndAccountAndChannel (options: { 203type GetVideoResult <T> = Promise<{
215 videoObject: { id: string } | string, 204 video: T
216 syncParam?: SyncParam, 205 created: boolean
217 fetchType?: 'all', 206 autoBlacklisted?: boolean
207}>
208
209type GetVideoParamAll = {
210 videoObject: { id: string } | string
211 syncParam?: SyncParam
212 fetchType?: 'all'
218 allowRefresh?: boolean 213 allowRefresh?: boolean
219}): Promise<{ video: MVideoAccountLightBlacklistAllFiles, created: boolean, autoBlacklisted?: boolean }> 214}
220function getOrCreateVideoAndAccountAndChannel (options: { 215
221 videoObject: { id: string } | string, 216type GetVideoParamImmutable = {
222 syncParam?: SyncParam, 217 videoObject: { id: string } | string
223 fetchType?: VideoFetchByUrlType, 218 syncParam?: SyncParam
219 fetchType: 'only-immutable-attributes'
220 allowRefresh: false
221}
222
223type GetVideoParamOther = {
224 videoObject: { id: string } | string
225 syncParam?: SyncParam
226 fetchType?: 'all' | 'only-video'
224 allowRefresh?: boolean 227 allowRefresh?: boolean
225}): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> 228}
226async function getOrCreateVideoAndAccountAndChannel (options: { 229
227 videoObject: { id: string } | string, 230function getOrCreateVideoAndAccountAndChannel (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
228 syncParam?: SyncParam, 231function getOrCreateVideoAndAccountAndChannel (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
229 fetchType?: VideoFetchByUrlType, 232function getOrCreateVideoAndAccountAndChannel (
230 allowRefresh?: boolean // true by default 233 options: GetVideoParamOther
231}): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> { 234): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail>
235async function getOrCreateVideoAndAccountAndChannel (
236 options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther
237): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> {
232 // Default params 238 // Default params
233 const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } 239 const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false }
234 const fetchType = options.fetchType || 'all' 240 const fetchType = options.fetchType || 'all'
@@ -236,18 +242,25 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
236 242
237 // Get video url 243 // Get video url
238 const videoUrl = getAPId(options.videoObject) 244 const videoUrl = getAPId(options.videoObject)
239
240 let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType) 245 let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType)
246
241 if (videoFromDatabase) { 247 if (videoFromDatabase) {
242 if (videoFromDatabase.isOutdated() && allowRefresh === true) { 248 // If allowRefresh is true, we could not call this function using 'only-immutable-attributes' fetch type
249 if (allowRefresh === true && (videoFromDatabase as MVideoThumbnail).isOutdated()) {
243 const refreshOptions = { 250 const refreshOptions = {
244 video: videoFromDatabase, 251 video: videoFromDatabase as MVideoThumbnail,
245 fetchedType: fetchType, 252 fetchedType: fetchType,
246 syncParam 253 syncParam
247 } 254 }
248 255
249 if (syncParam.refreshVideo === true) videoFromDatabase = await refreshVideoIfNeeded(refreshOptions) 256 if (syncParam.refreshVideo === true) {
250 else await JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: videoFromDatabase.url } }) 257 videoFromDatabase = await refreshVideoIfNeeded(refreshOptions)
258 } else {
259 await JobQueue.Instance.createJobWithPromise({
260 type: 'activitypub-refresher',
261 payload: { type: 'video', url: videoFromDatabase.url }
262 })
263 }
251 } 264 }
252 265
253 return { video: videoFromDatabase, created: false } 266 return { video: videoFromDatabase, created: false }
@@ -266,10 +279,10 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
266} 279}
267 280
268async function updateVideoFromAP (options: { 281async function updateVideoFromAP (options: {
269 video: MVideoAccountLightBlacklistAllFiles, 282 video: MVideoAccountLightBlacklistAllFiles
270 videoObject: VideoTorrentObject, 283 videoObject: VideoTorrentObject
271 account: MAccountIdActor, 284 account: MAccountIdActor
272 channel: MChannelDefault, 285 channel: MChannelDefault
273 overrideTo?: string[] 286 overrideTo?: string[]
274}) { 287}) {
275 const { video, videoObject, account, channel, overrideTo } = options 288 const { video, videoObject, account, channel, overrideTo } = options
@@ -284,7 +297,7 @@ async function updateVideoFromAP (options: {
284 let thumbnailModel: MThumbnail 297 let thumbnailModel: MThumbnail
285 298
286 try { 299 try {
287 thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) 300 thumbnailModel = await createVideoMiniatureFromUrl(getThumbnailFromIcons(videoObject).url, video, ThumbnailType.MINIATURE)
288 } catch (err) { 301 } catch (err) {
289 logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) 302 logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })
290 } 303 }
@@ -300,7 +313,7 @@ async function updateVideoFromAP (options: {
300 throw new Error('Account ' + account.Actor.url + ' does not own video channel ' + videoChannel.Actor.url) 313 throw new Error('Account ' + account.Actor.url + ' does not own video channel ' + videoChannel.Actor.url)
301 } 314 }
302 315
303 const to = overrideTo ? overrideTo : videoObject.to 316 const to = overrideTo || videoObject.to
304 const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, to) 317 const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, to)
305 video.name = videoData.name 318 video.name = videoData.name
306 video.uuid = videoData.uuid 319 video.uuid = videoData.uuid
@@ -327,8 +340,7 @@ async function updateVideoFromAP (options: {
327 340
328 if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t) 341 if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t)
329 342
330 // FIXME: use icon URL instead 343 const previewUrl = videoUpdated.getPreview().getFileUrl(videoUpdated)
331 const previewUrl = buildRemoteBaseUrl(videoUpdated, join(STATIC_PATHS.PREVIEWS, videoUpdated.getPreview().filename))
332 const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) 344 const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
333 await videoUpdated.addAndSaveThumbnail(previewModel, t) 345 await videoUpdated.addAndSaveThumbnail(previewModel, t)
334 346
@@ -391,7 +403,7 @@ async function updateVideoFromAP (options: {
391 await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) 403 await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t)
392 404
393 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { 405 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
394 return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, t) 406 return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, c.url, t)
395 }) 407 })
396 await Promise.all(videoCaptionsPromises) 408 await Promise.all(videoCaptionsPromises)
397 } 409 }
@@ -424,8 +436,8 @@ async function updateVideoFromAP (options: {
424} 436}
425 437
426async function refreshVideoIfNeeded (options: { 438async function refreshVideoIfNeeded (options: {
427 video: MVideoThumbnail, 439 video: MVideoThumbnail
428 fetchedType: VideoFetchByUrlType, 440 fetchedType: VideoFetchByUrlType
429 syncParam: SyncParam 441 syncParam: SyncParam
430}): Promise<MVideoThumbnail> { 442}): Promise<MVideoThumbnail> {
431 if (!options.video.isOutdated()) return options.video 443 if (!options.video.isOutdated()) return options.video
@@ -483,7 +495,6 @@ export {
483 federateVideoIfNeeded, 495 federateVideoIfNeeded,
484 fetchRemoteVideo, 496 fetchRemoteVideo,
485 getOrCreateVideoAndAccountAndChannel, 497 getOrCreateVideoAndAccountAndChannel,
486 fetchRemoteVideoStaticFile,
487 fetchRemoteVideoDescription, 498 fetchRemoteVideoDescription,
488 getOrCreateVideoChannelFromVideoObject 499 getOrCreateVideoChannelFromVideoObject
489} 500}
@@ -519,7 +530,7 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc
519 const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to) 530 const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to)
520 const video = VideoModel.build(videoData) as MVideoThumbnail 531 const video = VideoModel.build(videoData) as MVideoThumbnail
521 532
522 const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) 533 const promiseThumbnail = createVideoMiniatureFromUrl(getThumbnailFromIcons(videoObject).url, video, ThumbnailType.MINIATURE)
523 534
524 let thumbnailModel: MThumbnail 535 let thumbnailModel: MThumbnail
525 if (waitThumbnail === true) { 536 if (waitThumbnail === true) {
@@ -534,9 +545,12 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc
534 545
535 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) 546 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
536 547
537 // FIXME: use icon URL instead 548 const previewIcon = getPreviewFromIcons(videoObject)
538 const previewUrl = buildRemoteBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName())) 549 const previewUrl = previewIcon
539 const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) 550 ? previewIcon.url
551 : buildRemoteVideoBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName()))
552 const previewModel = createPlaceholderThumbnail(previewUrl, videoCreated, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
553
540 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t) 554 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
541 555
542 // Process files 556 // Process files
@@ -567,7 +581,7 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc
567 581
568 // Process captions 582 // Process captions
569 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { 583 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
570 return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t) 584 return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, c.url, t)
571 }) 585 })
572 await Promise.all(videoCaptionsPromises) 586 await Promise.all(videoCaptionsPromises)
573 587
@@ -592,13 +606,13 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc
592 thumbnailModel = videoCreated.id 606 thumbnailModel = videoCreated.id
593 607
594 return thumbnailModel.save() 608 return thumbnailModel.save()
595 }) 609 }).catch(err => logger.error('Cannot create miniature from url.', { err }))
596 } 610 }
597 611
598 return { autoBlacklisted, videoCreated } 612 return { autoBlacklisted, videoCreated }
599} 613}
600 614
601async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) { 615function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) {
602 const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED 616 const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED
603 const duration = videoObject.duration.replace(/[^\d]+/, '') 617 const duration = videoObject.duration.replace(/[^\d]+/, '')
604 618
@@ -639,7 +653,6 @@ async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, vide
639 createdAt: new Date(videoObject.published), 653 createdAt: new Date(videoObject.published),
640 publishedAt: new Date(videoObject.published), 654 publishedAt: new Date(videoObject.published),
641 originallyPublishedAt: videoObject.originallyPublishedAt ? new Date(videoObject.originallyPublishedAt) : null, 655 originallyPublishedAt: videoObject.originallyPublishedAt ? new Date(videoObject.originallyPublishedAt) : null,
642 // FIXME: updatedAt does not seems to be considered by Sequelize
643 updatedAt: new Date(videoObject.updated), 656 updatedAt: new Date(videoObject.updated),
644 views: videoObject.views, 657 views: videoObject.views,
645 likes: 0, 658 likes: 0,
@@ -672,7 +685,7 @@ function videoFileActivityUrlToDBAttributes (
672 685
673 const mediaType = fileUrl.mediaType 686 const mediaType = fileUrl.mediaType
674 const attribute = { 687 const attribute = {
675 extname: MIMETYPES.VIDEO.MIMETYPE_EXT[ mediaType ], 688 extname: MIMETYPES.VIDEO.MIMETYPE_EXT[mediaType],
676 infoHash: parsed.infoHash, 689 infoHash: parsed.infoHash,
677 resolution: fileUrl.height, 690 resolution: fileUrl.height,
678 size: fileUrl.size, 691 size: fileUrl.size,
@@ -722,3 +735,19 @@ function streamingPlaylistActivityUrlToDBAttributes (video: MVideoId, videoObjec
722 735
723 return attributes 736 return attributes
724} 737}
738
739function getThumbnailFromIcons (videoObject: VideoTorrentObject) {
740 let validIcons = videoObject.icon.filter(i => i.width > THUMBNAILS_SIZE.minWidth)
741 // Fallback if there are not valid icons
742 if (validIcons.length === 0) validIcons = videoObject.icon
743
744 return minBy(validIcons, 'width')
745}
746
747function getPreviewFromIcons (videoObject: VideoTorrentObject) {
748 const validIcons = videoObject.icon.filter(i => i.width > PREVIEWS_SIZE.minWidth)
749
750 // FIXME: don't put a fallback here for compatibility with PeerTube <2.2
751
752 return maxBy(validIcons, 'width')
753}
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index 1d8a08ed0..572bd03bd 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -17,7 +17,7 @@ import { MAccountActor, MChannelActor, MVideo } from '../typings/models'
17 17
18export class ClientHtml { 18export class ClientHtml {
19 19
20 private static htmlCache: { [ path: string ]: string } = {} 20 private static htmlCache: { [path: string]: string } = {}
21 21
22 static invalidCache () { 22 static invalidCache () {
23 logger.info('Cleaning HTML cache.') 23 logger.info('Cleaning HTML cache.')
@@ -94,7 +94,7 @@ export class ClientHtml {
94 94
95 private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) { 95 private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) {
96 const path = ClientHtml.getIndexPath(req, res, paramLang) 96 const path = ClientHtml.getIndexPath(req, res, paramLang)
97 if (ClientHtml.htmlCache[ path ]) return ClientHtml.htmlCache[ path ] 97 if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path]
98 98
99 const buffer = await readFile(path) 99 const buffer = await readFile(path)
100 100
@@ -104,7 +104,7 @@ export class ClientHtml {
104 html = ClientHtml.addCustomCSS(html) 104 html = ClientHtml.addCustomCSS(html)
105 html = await ClientHtml.addAsyncPluginCSS(html) 105 html = await ClientHtml.addAsyncPluginCSS(html)
106 106
107 ClientHtml.htmlCache[ path ] = html 107 ClientHtml.htmlCache[path] = html
108 108
109 return html 109 return html
110 } 110 }
@@ -214,21 +214,21 @@ export class ClientHtml {
214 const schemaTags = { 214 const schemaTags = {
215 '@context': 'http://schema.org', 215 '@context': 'http://schema.org',
216 '@type': 'VideoObject', 216 '@type': 'VideoObject',
217 name: videoNameEscaped, 217 'name': videoNameEscaped,
218 description: videoDescriptionEscaped, 218 'description': videoDescriptionEscaped,
219 thumbnailUrl: previewUrl, 219 'thumbnailUrl': previewUrl,
220 uploadDate: video.createdAt.toISOString(), 220 'uploadDate': video.createdAt.toISOString(),
221 duration: getActivityStreamDuration(video.duration), 221 'duration': getActivityStreamDuration(video.duration),
222 contentUrl: videoUrl, 222 'contentUrl': videoUrl,
223 embedUrl: embedUrl, 223 'embedUrl': embedUrl,
224 interactionCount: video.views 224 'interactionCount': video.views
225 } 225 }
226 226
227 let tagsString = '' 227 let tagsString = ''
228 228
229 // Opengraph 229 // Opengraph
230 Object.keys(openGraphMetaTags).forEach(tagName => { 230 Object.keys(openGraphMetaTags).forEach(tagName => {
231 const tagValue = openGraphMetaTags[ tagName ] 231 const tagValue = openGraphMetaTags[tagName]
232 232
233 tagsString += `<meta property="${tagName}" content="${tagValue}" />` 233 tagsString += `<meta property="${tagName}" content="${tagValue}" />`
234 }) 234 })
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index 7484524a4..9ce6186b1 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -32,7 +32,8 @@ class Emailer {
32 private initialized = false 32 private initialized = false
33 private transporter: Transporter 33 private transporter: Transporter
34 34
35 private constructor () {} 35 private constructor () {
36 }
36 37
37 init () { 38 init () {
38 // Already initialized 39 // Already initialized
@@ -97,12 +98,12 @@ class Emailer {
97 const channelName = video.VideoChannel.getDisplayName() 98 const channelName = video.VideoChannel.getDisplayName()
98 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 99 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
99 100
100 const text = `Hi dear user,\n\n` + 101 const text = 'Hi dear user,\n\n' +
101 `Your subscription ${channelName} just published a new video: ${video.name}` + 102 `Your subscription ${channelName} just published a new video: ${video.name}` +
102 `\n\n` + 103 '\n\n' +
103 `You can view it on ${videoUrl} ` + 104 `You can view it on ${videoUrl} ` +
104 `\n\n` + 105 '\n\n' +
105 `Cheers,\n` + 106 'Cheers,\n' +
106 `${CONFIG.EMAIL.BODY.SIGNATURE}` 107 `${CONFIG.EMAIL.BODY.SIGNATURE}`
107 108
108 const emailPayload: EmailPayload = { 109 const emailPayload: EmailPayload = {
@@ -118,10 +119,10 @@ class Emailer {
118 const followerName = actorFollow.ActorFollower.Account.getDisplayName() 119 const followerName = actorFollow.ActorFollower.Account.getDisplayName()
119 const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() 120 const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
120 121
121 const text = `Hi dear user,\n\n` + 122 const text = 'Hi dear user,\n\n' +
122 `Your ${followType} ${followingName} has a new subscriber: ${followerName}` + 123 `Your ${followType} ${followingName} has a new subscriber: ${followerName}` +
123 `\n\n` + 124 '\n\n' +
124 `Cheers,\n` + 125 'Cheers,\n' +
125 `${CONFIG.EMAIL.BODY.SIGNATURE}` 126 `${CONFIG.EMAIL.BODY.SIGNATURE}`
126 127
127 const emailPayload: EmailPayload = { 128 const emailPayload: EmailPayload = {
@@ -136,10 +137,10 @@ class Emailer {
136 addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) { 137 addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) {
137 const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' 138 const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : ''
138 139
139 const text = `Hi dear admin,\n\n` + 140 const text = 'Hi dear admin,\n\n' +
140 `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` + 141 `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` +
141 `\n\n` + 142 '\n\n' +
142 `Cheers,\n` + 143 'Cheers,\n' +
143 `${CONFIG.EMAIL.BODY.SIGNATURE}` 144 `${CONFIG.EMAIL.BODY.SIGNATURE}`
144 145
145 const emailPayload: EmailPayload = { 146 const emailPayload: EmailPayload = {
@@ -152,10 +153,10 @@ class Emailer {
152 } 153 }
153 154
154 addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) { 155 addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) {
155 const text = `Hi dear admin,\n\n` + 156 const text = 'Hi dear admin,\n\n' +
156 `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` + 157 `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` +
157 `\n\n` + 158 '\n\n' +
158 `Cheers,\n` + 159 'Cheers,\n' +
159 `${CONFIG.EMAIL.BODY.SIGNATURE}` 160 `${CONFIG.EMAIL.BODY.SIGNATURE}`
160 161
161 const emailPayload: EmailPayload = { 162 const emailPayload: EmailPayload = {
@@ -170,12 +171,12 @@ class Emailer {
170 myVideoPublishedNotification (to: string[], video: MVideo) { 171 myVideoPublishedNotification (to: string[], video: MVideo) {
171 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 172 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
172 173
173 const text = `Hi dear user,\n\n` + 174 const text = 'Hi dear user,\n\n' +
174 `Your video ${video.name} has been published.` + 175 `Your video ${video.name} has been published.` +
175 `\n\n` + 176 '\n\n' +
176 `You can view it on ${videoUrl} ` + 177 `You can view it on ${videoUrl} ` +
177 `\n\n` + 178 '\n\n' +
178 `Cheers,\n` + 179 'Cheers,\n' +
179 `${CONFIG.EMAIL.BODY.SIGNATURE}` 180 `${CONFIG.EMAIL.BODY.SIGNATURE}`
180 181
181 const emailPayload: EmailPayload = { 182 const emailPayload: EmailPayload = {
@@ -190,12 +191,12 @@ class Emailer {
190 myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) { 191 myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) {
191 const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() 192 const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
192 193
193 const text = `Hi dear user,\n\n` + 194 const text = 'Hi dear user,\n\n' +
194 `Your video import ${videoImport.getTargetIdentifier()} is finished.` + 195 `Your video import ${videoImport.getTargetIdentifier()} is finished.` +
195 `\n\n` + 196 '\n\n' +
196 `You can view the imported video on ${videoUrl} ` + 197 `You can view the imported video on ${videoUrl} ` +
197 `\n\n` + 198 '\n\n' +
198 `Cheers,\n` + 199 'Cheers,\n' +
199 `${CONFIG.EMAIL.BODY.SIGNATURE}` 200 `${CONFIG.EMAIL.BODY.SIGNATURE}`
200 201
201 const emailPayload: EmailPayload = { 202 const emailPayload: EmailPayload = {
@@ -210,12 +211,12 @@ class Emailer {
210 myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) { 211 myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) {
211 const importUrl = WEBSERVER.URL + '/my-account/video-imports' 212 const importUrl = WEBSERVER.URL + '/my-account/video-imports'
212 213
213 const text = `Hi dear user,\n\n` + 214 const text = 'Hi dear user,\n\n' +
214 `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` + 215 `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` +
215 `\n\n` + 216 '\n\n' +
216 `See your videos import dashboard for more information: ${importUrl}` + 217 `See your videos import dashboard for more information: ${importUrl}` +
217 `\n\n` + 218 '\n\n' +
218 `Cheers,\n` + 219 'Cheers,\n' +
219 `${CONFIG.EMAIL.BODY.SIGNATURE}` 220 `${CONFIG.EMAIL.BODY.SIGNATURE}`
220 221
221 const emailPayload: EmailPayload = { 222 const emailPayload: EmailPayload = {
@@ -232,12 +233,12 @@ class Emailer {
232 const video = comment.Video 233 const video = comment.Video
233 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() 234 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
234 235
235 const text = `Hi dear user,\n\n` + 236 const text = 'Hi dear user,\n\n' +
236 `A new comment has been posted by ${accountName} on your video ${video.name}` + 237 `A new comment has been posted by ${accountName} on your video ${video.name}` +
237 `\n\n` + 238 '\n\n' +
238 `You can view it on ${commentUrl} ` + 239 `You can view it on ${commentUrl} ` +
239 `\n\n` + 240 '\n\n' +
240 `Cheers,\n` + 241 'Cheers,\n' +
241 `${CONFIG.EMAIL.BODY.SIGNATURE}` 242 `${CONFIG.EMAIL.BODY.SIGNATURE}`
242 243
243 const emailPayload: EmailPayload = { 244 const emailPayload: EmailPayload = {
@@ -254,12 +255,12 @@ class Emailer {
254 const video = comment.Video 255 const video = comment.Video
255 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() 256 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
256 257
257 const text = `Hi dear user,\n\n` + 258 const text = 'Hi dear user,\n\n' +
258 `${accountName} mentioned you on video ${video.name}` + 259 `${accountName} mentioned you on video ${video.name}` +
259 `\n\n` + 260 '\n\n' +
260 `You can view the comment on ${commentUrl} ` + 261 `You can view the comment on ${commentUrl} ` +
261 `\n\n` + 262 '\n\n' +
262 `Cheers,\n` + 263 'Cheers,\n' +
263 `${CONFIG.EMAIL.BODY.SIGNATURE}` 264 `${CONFIG.EMAIL.BODY.SIGNATURE}`
264 265
265 const emailPayload: EmailPayload = { 266 const emailPayload: EmailPayload = {
@@ -274,9 +275,9 @@ class Emailer {
274 addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) { 275 addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) {
275 const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() 276 const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath()
276 277
277 const text = `Hi,\n\n` + 278 const text = 'Hi,\n\n' +
278 `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` + 279 `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` +
279 `Cheers,\n` + 280 'Cheers,\n' +
280 `${CONFIG.EMAIL.BODY.SIGNATURE}` 281 `${CONFIG.EMAIL.BODY.SIGNATURE}`
281 282
282 const emailPayload: EmailPayload = { 283 const emailPayload: EmailPayload = {
@@ -292,14 +293,14 @@ class Emailer {
292 const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' 293 const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
293 const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() 294 const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
294 295
295 const text = `Hi,\n\n` + 296 const text = 'Hi,\n\n' +
296 `A recently added video was auto-blacklisted and requires moderator review before publishing.` + 297 'A recently added video was auto-blacklisted and requires moderator review before publishing.' +
297 `\n\n` + 298 '\n\n' +
298 `You can view it and take appropriate action on ${videoUrl}` + 299 `You can view it and take appropriate action on ${videoUrl}` +
299 `\n\n` + 300 '\n\n' +
300 `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` + 301 `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` +
301 `\n\n` + 302 '\n\n' +
302 `Cheers,\n` + 303 'Cheers,\n' +
303 `${CONFIG.EMAIL.BODY.SIGNATURE}` 304 `${CONFIG.EMAIL.BODY.SIGNATURE}`
304 305
305 const emailPayload: EmailPayload = { 306 const emailPayload: EmailPayload = {
@@ -312,9 +313,9 @@ class Emailer {
312 } 313 }
313 314
314 addNewUserRegistrationNotification (to: string[], user: MUser) { 315 addNewUserRegistrationNotification (to: string[], user: MUser) {
315 const text = `Hi,\n\n` + 316 const text = 'Hi,\n\n' +
316 `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + 317 `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` +
317 `Cheers,\n` + 318 'Cheers,\n' +
318 `${CONFIG.EMAIL.BODY.SIGNATURE}` 319 `${CONFIG.EMAIL.BODY.SIGNATURE}`
319 320
320 const emailPayload: EmailPayload = { 321 const emailPayload: EmailPayload = {
@@ -367,11 +368,11 @@ class Emailer {
367 } 368 }
368 369
369 addPasswordResetEmailJob (to: string, resetPasswordUrl: string) { 370 addPasswordResetEmailJob (to: string, resetPasswordUrl: string) {
370 const text = `Hi dear user,\n\n` + 371 const text = 'Hi dear user,\n\n' +
371 `A reset password procedure for your account ${to} has been requested on ${WEBSERVER.HOST} ` + 372 `A reset password procedure for your account ${to} has been requested on ${WEBSERVER.HOST} ` +
372 `Please follow this link to reset it: ${resetPasswordUrl} (the link will expire within 1 hour)\n\n` + 373 `Please follow this link to reset it: ${resetPasswordUrl} (the link will expire within 1 hour)\n\n` +
373 `If you are not the person who initiated this request, please ignore this email.\n\n` + 374 'If you are not the person who initiated this request, please ignore this email.\n\n' +
374 `Cheers,\n` + 375 'Cheers,\n' +
375 `${CONFIG.EMAIL.BODY.SIGNATURE}` 376 `${CONFIG.EMAIL.BODY.SIGNATURE}`
376 377
377 const emailPayload: EmailPayload = { 378 const emailPayload: EmailPayload = {
@@ -384,11 +385,11 @@ class Emailer {
384 } 385 }
385 386
386 addVerifyEmailJob (to: string, verifyEmailUrl: string) { 387 addVerifyEmailJob (to: string, verifyEmailUrl: string) {
387 const text = `Welcome to PeerTube,\n\n` + 388 const text = 'Welcome to PeerTube,\n\n' +
388 `To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` + 389 `To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` +
389 `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + 390 `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` +
390 `If you are not the person who initiated this request, please ignore this email.\n\n` + 391 'If you are not the person who initiated this request, please ignore this email.\n\n' +
391 `Cheers,\n` + 392 'Cheers,\n' +
392 `${CONFIG.EMAIL.BODY.SIGNATURE}` 393 `${CONFIG.EMAIL.BODY.SIGNATURE}`
393 394
394 const emailPayload: EmailPayload = { 395 const emailPayload: EmailPayload = {
diff --git a/server/lib/files-cache/videos-caption-cache.ts b/server/lib/files-cache/videos-caption-cache.ts
index 440c3fde8..26ab3bd0d 100644
--- a/server/lib/files-cache/videos-caption-cache.ts
+++ b/server/lib/files-cache/videos-caption-cache.ts
@@ -5,7 +5,7 @@ import { VideoCaptionModel } from '../../models/video/video-caption'
5import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' 5import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
6import { CONFIG } from '../../initializers/config' 6import { CONFIG } from '../../initializers/config'
7import { logger } from '../../helpers/logger' 7import { logger } from '../../helpers/logger'
8import { fetchRemoteVideoStaticFile } from '../activitypub' 8import { doRequestAndSaveToFile } from '@server/helpers/requests'
9 9
10type GetPathParam = { videoId: string, language: string } 10type GetPathParam = { videoId: string, language: string }
11 11
@@ -46,11 +46,10 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> {
46 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) 46 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
47 if (!video) return undefined 47 if (!video) return undefined
48 48
49 // FIXME: use URL 49 const remoteUrl = videoCaption.getFileUrl(video)
50 const remoteStaticPath = videoCaption.getCaptionStaticPath()
51 const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) 50 const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName())
52 51
53 await fetchRemoteVideoStaticFile(video, remoteStaticPath, destPath) 52 await doRequestAndSaveToFile({ uri: remoteUrl }, destPath)
54 53
55 return { isOwned: false, path: destPath } 54 return { isOwned: false, path: destPath }
56 } 55 }
diff --git a/server/lib/files-cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts
index 3da6bb138..d0d4fc5b5 100644
--- a/server/lib/files-cache/videos-preview-cache.ts
+++ b/server/lib/files-cache/videos-preview-cache.ts
@@ -1,9 +1,8 @@
1import { join } from 'path' 1import { join } from 'path'
2import { FILES_CACHE, STATIC_PATHS } from '../../initializers/constants' 2import { FILES_CACHE } from '../../initializers/constants'
3import { VideoModel } from '../../models/video/video' 3import { VideoModel } from '../../models/video/video'
4import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' 4import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
5import { CONFIG } from '../../initializers/config' 5import { doRequestAndSaveToFile } from '@server/helpers/requests'
6import { fetchRemoteVideoStaticFile } from '../activitypub'
7 6
8class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { 7class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
9 8
@@ -32,11 +31,11 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
32 31
33 if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.') 32 if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.')
34 33
35 // FIXME: use URL 34 const preview = video.getPreview()
36 const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreview().filename) 35 const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, preview.filename)
37 const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, video.getPreview().filename)
38 36
39 await fetchRemoteVideoStaticFile(video, remoteStaticPath, destPath) 37 const remoteUrl = preview.getFileUrl(video)
38 await doRequestAndSaveToFile({ uri: remoteUrl }, destPath)
40 39
41 return { isOwned: false, path: destPath } 40 return { isOwned: false, path: destPath }
42 } 41 }
diff --git a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts
index 0ff7b44a0..7d9dd61e9 100644
--- a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts
+++ b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts
@@ -5,11 +5,13 @@ import { doRequest } from '../../../helpers/requests'
5import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' 5import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils'
6import { BROADCAST_CONCURRENCY, JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' 6import { BROADCAST_CONCURRENCY, JOB_REQUEST_TIMEOUT } from '../../../initializers/constants'
7import { ActorFollowScoreCache } from '../../files-cache' 7import { ActorFollowScoreCache } from '../../files-cache'
8import { ContextType } from '@server/helpers/activitypub'
8 9
9export type ActivitypubHttpBroadcastPayload = { 10export type ActivitypubHttpBroadcastPayload = {
10 uris: string[] 11 uris: string[]
11 signatureActorId?: number 12 signatureActorId?: number
12 body: any 13 body: any
14 contextType?: ContextType
13} 15}
14 16
15async function processActivityPubHttpBroadcast (job: Bull.Job) { 17async function processActivityPubHttpBroadcast (job: Bull.Job) {
diff --git a/server/lib/job-queue/handlers/activitypub-http-unicast.ts b/server/lib/job-queue/handlers/activitypub-http-unicast.ts
index c70ce3be9..6b71e2891 100644
--- a/server/lib/job-queue/handlers/activitypub-http-unicast.ts
+++ b/server/lib/job-queue/handlers/activitypub-http-unicast.ts
@@ -4,11 +4,13 @@ import { doRequest } from '../../../helpers/requests'
4import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' 4import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils'
5import { JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' 5import { JOB_REQUEST_TIMEOUT } from '../../../initializers/constants'
6import { ActorFollowScoreCache } from '../../files-cache' 6import { ActorFollowScoreCache } from '../../files-cache'
7import { ContextType } from '@server/helpers/activitypub'
7 8
8export type ActivitypubHttpUnicastPayload = { 9export type ActivitypubHttpUnicastPayload = {
9 uri: string 10 uri: string
10 signatureActorId?: number 11 signatureActorId?: number
11 body: any 12 body: any
13 contextType?: ContextType
12} 14}
13 15
14async function processActivityPubHttpUnicast (job: Bull.Job) { 16async function processActivityPubHttpUnicast (job: Bull.Job) {
diff --git a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
index d3bde6e6a..54b35840d 100644
--- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
+++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
@@ -1,11 +1,11 @@
1import { buildSignedActivity } from '../../../../helpers/activitypub' 1import { buildSignedActivity, ContextType } from '../../../../helpers/activitypub'
2import { getServerActor } from '../../../../helpers/utils' 2import { getServerActor } from '../../../../helpers/utils'
3import { ActorModel } from '../../../../models/activitypub/actor' 3import { ActorModel } from '../../../../models/activitypub/actor'
4import { sha256 } from '../../../../helpers/core-utils' 4import { sha256 } from '../../../../helpers/core-utils'
5import { HTTP_SIGNATURE } from '../../../../initializers/constants' 5import { HTTP_SIGNATURE } from '../../../../initializers/constants'
6import { MActor } from '../../../../typings/models' 6import { MActor } from '../../../../typings/models'
7 7
8type Payload = { body: any, signatureActorId?: number } 8type Payload = { body: any, contextType?: ContextType, signatureActorId?: number }
9 9
10async function computeBody (payload: Payload) { 10async function computeBody (payload: Payload) {
11 let body = payload.body 11 let body = payload.body
@@ -13,7 +13,7 @@ async function computeBody (payload: Payload) {
13 if (payload.signatureActorId) { 13 if (payload.signatureActorId) {
14 const actorSignature = await ActorModel.load(payload.signatureActorId) 14 const actorSignature = await ActorModel.load(payload.signatureActorId)
15 if (!actorSignature) throw new Error('Unknown signature actor id.') 15 if (!actorSignature) throw new Error('Unknown signature actor id.')
16 body = await buildSignedActivity(actorSignature, payload.body) 16 body = await buildSignedActivity(actorSignature, payload.body, payload.contextType)
17 } 17 }
18 18
19 return body 19 return body
@@ -42,7 +42,7 @@ async function buildSignedRequestOptions (payload: Payload) {
42 42
43function buildGlobalHeaders (body: any) { 43function buildGlobalHeaders (body: any) {
44 return { 44 return {
45 'Digest': buildDigest(body) 45 Digest: buildDigest(body)
46 } 46 }
47} 47}
48 48
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts
index 99c991e72..be9e7d181 100644
--- a/server/lib/job-queue/handlers/video-file-import.ts
+++ b/server/lib/job-queue/handlers/video-file-import.ts
@@ -11,7 +11,7 @@ import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
11import { getVideoFilePath } from '@server/lib/video-paths' 11import { getVideoFilePath } from '@server/lib/video-paths'
12 12
13export type VideoFileImportPayload = { 13export type VideoFileImportPayload = {
14 videoUUID: string, 14 videoUUID: string
15 filePath: string 15 filePath: string
16} 16}
17 17
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index 1fca17584..09f225cec 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -221,7 +221,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
221 isNewVideo: true 221 isNewVideo: true
222 } 222 }
223 223
224 await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) 224 await JobQueue.Instance.createJobWithPromise({ type: 'video-transcoding', payload: dataInput })
225 } 225 }
226 226
227 } catch (err) { 227 } catch (err) {
diff --git a/server/lib/job-queue/handlers/video-redundancy.ts b/server/lib/job-queue/handlers/video-redundancy.ts
new file mode 100644
index 000000000..319d7090e
--- /dev/null
+++ b/server/lib/job-queue/handlers/video-redundancy.ts
@@ -0,0 +1,20 @@
1import * as Bull from 'bull'
2import { logger } from '../../../helpers/logger'
3import { VideosRedundancyScheduler } from '@server/lib/schedulers/videos-redundancy-scheduler'
4
5export type VideoRedundancyPayload = {
6 videoId: number
7}
8
9async function processVideoRedundancy (job: Bull.Job) {
10 const payload = job.data as VideoRedundancyPayload
11 logger.info('Processing video redundancy in job %d.', job.id)
12
13 return VideosRedundancyScheduler.Instance.createManualRedundancy(payload.videoId)
14}
15
16// ---------------------------------------------------------------------------
17
18export {
19 processVideoRedundancy
20}
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts
index 39b9fac98..c020057c9 100644
--- a/server/lib/job-queue/handlers/video-transcoding.ts
+++ b/server/lib/job-queue/handlers/video-transcoding.ts
@@ -6,7 +6,6 @@ import { JobQueue } from '../job-queue'
6import { federateVideoIfNeeded } from '../../activitypub' 6import { federateVideoIfNeeded } from '../../activitypub'
7import { retryTransactionWrapper } from '../../../helpers/database-utils' 7import { retryTransactionWrapper } from '../../../helpers/database-utils'
8import { sequelizeTypescript } from '../../../initializers' 8import { sequelizeTypescript } from '../../../initializers'
9import * as Bluebird from 'bluebird'
10import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' 9import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils'
11import { generateHlsPlaylist, mergeAudioVideofile, optimizeOriginalVideofile, transcodeNewResolution } from '../../video-transcoding' 10import { generateHlsPlaylist, mergeAudioVideofile, optimizeOriginalVideofile, transcodeNewResolution } from '../../video-transcoding'
12import { Notifier } from '../../notifier' 11import { Notifier } from '../../notifier'
@@ -40,8 +39,11 @@ interface OptimizeTranscodingPayload extends BaseTranscodingPayload {
40 type: 'optimize' 39 type: 'optimize'
41} 40}
42 41
43export type VideoTranscodingPayload = HLSTranscodingPayload | NewResolutionTranscodingPayload 42export type VideoTranscodingPayload =
44 | OptimizeTranscodingPayload | MergeAudioTranscodingPayload 43 HLSTranscodingPayload
44 | NewResolutionTranscodingPayload
45 | OptimizeTranscodingPayload
46 | MergeAudioTranscodingPayload
45 47
46async function processVideoTranscoding (job: Bull.Job) { 48async function processVideoTranscoding (job: Bull.Job) {
47 const payload = job.data as VideoTranscodingPayload 49 const payload = job.data as VideoTranscodingPayload
@@ -105,7 +107,7 @@ async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: O
105 107
106 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { 108 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
107 // Maybe the video changed in database, refresh it 109 // Maybe the video changed in database, refresh it
108 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t) 110 const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t)
109 // Video does not exist anymore 111 // Video does not exist anymore
110 if (!videoDatabase) return undefined 112 if (!videoDatabase) return undefined
111 113
@@ -122,8 +124,6 @@ async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: O
122 await createHlsJobIfEnabled(hlsPayload) 124 await createHlsJobIfEnabled(hlsPayload)
123 125
124 if (resolutionsEnabled.length !== 0) { 126 if (resolutionsEnabled.length !== 0) {
125 const tasks: (Bluebird<Bull.Job<any>> | Promise<Bull.Job<any>>)[] = []
126
127 for (const resolution of resolutionsEnabled) { 127 for (const resolution of resolutionsEnabled) {
128 let dataInput: VideoTranscodingPayload 128 let dataInput: VideoTranscodingPayload
129 129
@@ -143,12 +143,9 @@ async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: O
143 } 143 }
144 } 144 }
145 145
146 const p = JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) 146 JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput })
147 tasks.push(p)
148 } 147 }
149 148
150 await Promise.all(tasks)
151
152 logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled }) 149 logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled })
153 } else { 150 } else {
154 // No transcoding to do, it's now published 151 // No transcoding to do, it's now published
diff --git a/server/lib/job-queue/handlers/video-views.ts b/server/lib/job-queue/handlers/video-views.ts
index 73fa5ed04..2258cd029 100644
--- a/server/lib/job-queue/handlers/video-views.ts
+++ b/server/lib/job-queue/handlers/video-views.ts
@@ -23,6 +23,8 @@ async function processVideosViews () {
23 for (const videoId of videoIds) { 23 for (const videoId of videoIds) {
24 try { 24 try {
25 const views = await Redis.Instance.getVideoViews(videoId, hour) 25 const views = await Redis.Instance.getVideoViews(videoId, hour)
26 await Redis.Instance.deleteVideoViews(videoId, hour)
27
26 if (views) { 28 if (views) {
27 logger.debug('Adding %d views to video %d in hour %d.', views, videoId, hour) 29 logger.debug('Adding %d views to video %d in hour %d.', views, videoId, hour)
28 30
@@ -52,8 +54,6 @@ async function processVideosViews () {
52 logger.error('Cannot create video views for video %d in hour %d.', videoId, hour, { err }) 54 logger.error('Cannot create video views for video %d in hour %d.', videoId, hour, { err })
53 } 55 }
54 } 56 }
55
56 await Redis.Instance.deleteVideoViews(videoId, hour)
57 } catch (err) { 57 } catch (err) {
58 logger.error('Cannot update video views of video %d in hour %d.', videoId, hour, { err }) 58 logger.error('Cannot update video views of video %d in hour %d.', videoId, hour, { err })
59 } 59 }
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts
index ec601e9ea..14acace7d 100644
--- a/server/lib/job-queue/job-queue.ts
+++ b/server/lib/job-queue/job-queue.ts
@@ -13,6 +13,7 @@ import { processVideoImport, VideoImportPayload } from './handlers/video-import'
13import { processVideosViews } from './handlers/video-views' 13import { processVideosViews } from './handlers/video-views'
14import { refreshAPObject, RefreshPayload } from './handlers/activitypub-refresher' 14import { refreshAPObject, RefreshPayload } from './handlers/activitypub-refresher'
15import { processVideoFileImport, VideoFileImportPayload } from './handlers/video-file-import' 15import { processVideoFileImport, VideoFileImportPayload } from './handlers/video-file-import'
16import { processVideoRedundancy, VideoRedundancyPayload } from '@server/lib/job-queue/handlers/video-redundancy'
16 17
17type CreateJobArgument = 18type CreateJobArgument =
18 { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | 19 { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } |
@@ -24,20 +25,21 @@ type CreateJobArgument =
24 { type: 'email', payload: EmailPayload } | 25 { type: 'email', payload: EmailPayload } |
25 { type: 'video-import', payload: VideoImportPayload } | 26 { type: 'video-import', payload: VideoImportPayload } |
26 { type: 'activitypub-refresher', payload: RefreshPayload } | 27 { type: 'activitypub-refresher', payload: RefreshPayload } |
27 { type: 'videos-views', payload: {} } 28 { type: 'videos-views', payload: {} } |
29 { type: 'video-redundancy', payload: VideoRedundancyPayload }
28 30
29const handlers: { [ id in (JobType | 'video-file') ]: (job: Bull.Job) => Promise<any>} = { 31const handlers: { [id in JobType]: (job: Bull.Job) => Promise<any> } = {
30 'activitypub-http-broadcast': processActivityPubHttpBroadcast, 32 'activitypub-http-broadcast': processActivityPubHttpBroadcast,
31 'activitypub-http-unicast': processActivityPubHttpUnicast, 33 'activitypub-http-unicast': processActivityPubHttpUnicast,
32 'activitypub-http-fetcher': processActivityPubHttpFetcher, 34 'activitypub-http-fetcher': processActivityPubHttpFetcher,
33 'activitypub-follow': processActivityPubFollow, 35 'activitypub-follow': processActivityPubFollow,
34 'video-file-import': processVideoFileImport, 36 'video-file-import': processVideoFileImport,
35 'video-transcoding': processVideoTranscoding, 37 'video-transcoding': processVideoTranscoding,
36 'video-file': processVideoTranscoding, // TODO: remove it (changed in 1.3)
37 'email': processEmail, 38 'email': processEmail,
38 'video-import': processVideoImport, 39 'video-import': processVideoImport,
39 'videos-views': processVideosViews, 40 'videos-views': processVideosViews,
40 'activitypub-refresher': refreshAPObject 41 'activitypub-refresher': refreshAPObject,
42 'video-redundancy': processVideoRedundancy
41} 43}
42 44
43const jobTypes: JobType[] = [ 45const jobTypes: JobType[] = [
@@ -50,20 +52,22 @@ const jobTypes: JobType[] = [
50 'video-file-import', 52 'video-file-import',
51 'video-import', 53 'video-import',
52 'videos-views', 54 'videos-views',
53 'activitypub-refresher' 55 'activitypub-refresher',
56 'video-redundancy'
54] 57]
55 58
56class JobQueue { 59class JobQueue {
57 60
58 private static instance: JobQueue 61 private static instance: JobQueue
59 62
60 private queues: { [ id in JobType ]?: Bull.Queue } = {} 63 private queues: { [id in JobType]?: Bull.Queue } = {}
61 private initialized = false 64 private initialized = false
62 private jobRedisPrefix: string 65 private jobRedisPrefix: string
63 66
64 private constructor () {} 67 private constructor () {
68 }
65 69
66 async init () { 70 init () {
67 // Already initialized 71 // Already initialized
68 if (this.initialized === true) return 72 if (this.initialized === true) return
69 this.initialized = true 73 this.initialized = true
@@ -105,11 +109,16 @@ class JobQueue {
105 } 109 }
106 } 110 }
107 111
108 createJob (obj: CreateJobArgument) { 112 createJob (obj: CreateJobArgument): void {
113 this.createJobWithPromise(obj)
114 .catch(err => logger.error('Cannot create job.', { err, obj }))
115 }
116
117 createJobWithPromise (obj: CreateJobArgument) {
109 const queue = this.queues[obj.type] 118 const queue = this.queues[obj.type]
110 if (queue === undefined) { 119 if (queue === undefined) {
111 logger.error('Unknown queue %s: cannot create job.', obj.type) 120 logger.error('Unknown queue %s: cannot create job.', obj.type)
112 throw Error('Unknown queue, cannot create job') 121 return
113 } 122 }
114 123
115 const jobArgs: Bull.JobOptions = { 124 const jobArgs: Bull.JobOptions = {
@@ -122,10 +131,10 @@ class JobQueue {
122 } 131 }
123 132
124 async listForApi (options: { 133 async listForApi (options: {
125 state: JobState, 134 state: JobState
126 start: number, 135 start: number
127 count: number, 136 count: number
128 asc?: boolean, 137 asc?: boolean
129 jobType: JobType 138 jobType: JobType
130 }): Promise<Bull.Job[]> { 139 }): Promise<Bull.Job[]> {
131 const { state, start, count, asc, jobType } = options 140 const { state, start, count, asc, jobType } = options
@@ -133,16 +142,14 @@ class JobQueue {
133 142
134 const filteredJobTypes = this.filterJobTypes(jobType) 143 const filteredJobTypes = this.filterJobTypes(jobType)
135 144
136 // TODO: optimize
137 for (const jobType of filteredJobTypes) { 145 for (const jobType of filteredJobTypes) {
138 const queue = this.queues[ jobType ] 146 const queue = this.queues[jobType]
139 if (queue === undefined) { 147 if (queue === undefined) {
140 logger.error('Unknown queue %s to list jobs.', jobType) 148 logger.error('Unknown queue %s to list jobs.', jobType)
141 continue 149 continue
142 } 150 }
143 151
144 // FIXME: Bull queue typings does not have getJobs method 152 const jobs = await queue.getJobs([ state ], 0, start + count, asc)
145 const jobs = await (queue as any).getJobs(state, 0, start + count, asc)
146 results = results.concat(jobs) 153 results = results.concat(jobs)
147 } 154 }
148 155
@@ -164,7 +171,7 @@ class JobQueue {
164 const filteredJobTypes = this.filterJobTypes(jobType) 171 const filteredJobTypes = this.filterJobTypes(jobType)
165 172
166 for (const type of filteredJobTypes) { 173 for (const type of filteredJobTypes) {
167 const queue = this.queues[ type ] 174 const queue = this.queues[type]
168 if (queue === undefined) { 175 if (queue === undefined) {
169 logger.error('Unknown queue %s to count jobs.', type) 176 logger.error('Unknown queue %s to count jobs.', type)
170 continue 177 continue
@@ -172,7 +179,7 @@ class JobQueue {
172 179
173 const counts = await queue.getJobCounts() 180 const counts = await queue.getJobCounts()
174 181
175 total += counts[ state ] 182 total += counts[state]
176 } 183 }
177 184
178 return total 185 return total
@@ -188,7 +195,7 @@ class JobQueue {
188 private addRepeatableJobs () { 195 private addRepeatableJobs () {
189 this.queues['videos-views'].add({}, { 196 this.queues['videos-views'].add({}, {
190 repeat: REPEAT_JOBS['videos-views'] 197 repeat: REPEAT_JOBS['videos-views']
191 }) 198 }).catch(err => logger.error('Cannot add repeatable job.', { err }))
192 } 199 }
193 200
194 private filterJobTypes (jobType?: JobType) { 201 private filterJobTypes (jobType?: JobType) {
diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts
index b609f4585..55f7a985d 100644
--- a/server/lib/moderation.ts
+++ b/server/lib/moderation.ts
@@ -15,41 +15,41 @@ export type AcceptResult = {
15 15
16// Can be filtered by plugins 16// Can be filtered by plugins
17function isLocalVideoAccepted (object: { 17function isLocalVideoAccepted (object: {
18 videoBody: VideoCreate, 18 videoBody: VideoCreate
19 videoFile: Express.Multer.File & { duration?: number }, 19 videoFile: Express.Multer.File & { duration?: number }
20 user: UserModel 20 user: UserModel
21}): AcceptResult { 21}): AcceptResult {
22 return { accepted: true } 22 return { accepted: true }
23} 23}
24 24
25function isLocalVideoThreadAccepted (_object: { 25function isLocalVideoThreadAccepted (_object: {
26 commentBody: VideoCommentCreate, 26 commentBody: VideoCommentCreate
27 video: VideoModel, 27 video: VideoModel
28 user: UserModel 28 user: UserModel
29}): AcceptResult { 29}): AcceptResult {
30 return { accepted: true } 30 return { accepted: true }
31} 31}
32 32
33function isLocalVideoCommentReplyAccepted (_object: { 33function isLocalVideoCommentReplyAccepted (_object: {
34 commentBody: VideoCommentCreate, 34 commentBody: VideoCommentCreate
35 parentComment: VideoCommentModel, 35 parentComment: VideoCommentModel
36 video: VideoModel, 36 video: VideoModel
37 user: UserModel 37 user: UserModel
38}): AcceptResult { 38}): AcceptResult {
39 return { accepted: true } 39 return { accepted: true }
40} 40}
41 41
42function isRemoteVideoAccepted (_object: { 42function isRemoteVideoAccepted (_object: {
43 activity: ActivityCreate, 43 activity: ActivityCreate
44 videoAP: VideoTorrentObject, 44 videoAP: VideoTorrentObject
45 byActor: ActorModel 45 byActor: ActorModel
46}): AcceptResult { 46}): AcceptResult {
47 return { accepted: true } 47 return { accepted: true }
48} 48}
49 49
50function isRemoteVideoCommentAccepted (_object: { 50function isRemoteVideoCommentAccepted (_object: {
51 activity: ActivityCreate, 51 activity: ActivityCreate
52 commentAP: VideoCommentObject, 52 commentAP: VideoCommentObject
53 byActor: ActorModel 53 byActor: ActorModel
54}): AcceptResult { 54}): AcceptResult {
55 return { accepted: true } 55 return { accepted: true }
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts
index 679b9bcf6..63197eee1 100644
--- a/server/lib/notifier.ts
+++ b/server/lib/notifier.ts
@@ -6,7 +6,6 @@ import { UserModel } from '../models/account/user'
6import { PeerTubeSocket } from './peertube-socket' 6import { PeerTubeSocket } from './peertube-socket'
7import { CONFIG } from '../initializers/config' 7import { CONFIG } from '../initializers/config'
8import { VideoPrivacy, VideoState } from '../../shared/models/videos' 8import { VideoPrivacy, VideoState } from '../../shared/models/videos'
9import * as Bluebird from 'bluebird'
10import { AccountBlocklistModel } from '../models/account/account-blocklist' 9import { AccountBlocklistModel } from '../models/account/account-blocklist'
11import { 10import {
12 MCommentOwnerVideo, 11 MCommentOwnerVideo,
@@ -17,7 +16,8 @@ import {
17 MVideoFullLight 16 MVideoFullLight
18} from '../typings/models/video' 17} from '../typings/models/video'
19import { 18import {
20 MUser, MUserAccount, 19 MUser,
20 MUserAccount,
21 MUserDefault, 21 MUserDefault,
22 MUserNotifSettingAccount, 22 MUserNotifSettingAccount,
23 MUserWithNotificationSetting, 23 MUserWithNotificationSetting,
@@ -32,14 +32,15 @@ class Notifier {
32 32
33 private static instance: Notifier 33 private static instance: Notifier
34 34
35 private constructor () {} 35 private constructor () {
36 }
36 37
37 notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void { 38 notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void {
38 // Only notify on public and published videos which are not blacklisted 39 // Only notify on public and published videos which are not blacklisted
39 if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return 40 if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return
40 41
41 this.notifySubscribersOfNewVideo(video) 42 this.notifySubscribersOfNewVideo(video)
42 .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) 43 .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
43 } 44 }
44 45
45 notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void { 46 notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void {
@@ -63,7 +64,9 @@ class Notifier {
63 if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return 64 if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
64 65
65 this.notifyOwnedVideoHasBeenPublished(video) 66 this.notifyOwnedVideoHasBeenPublished(video)
66 .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length 67 .catch(err => {
68 logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })
69 })
67 } 70 }
68 71
69 notifyOnNewComment (comment: MCommentOwnerVideo): void { 72 notifyOnNewComment (comment: MCommentOwnerVideo): void {
@@ -76,17 +79,17 @@ class Notifier {
76 79
77 notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void { 80 notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void {
78 this.notifyModeratorsOfNewVideoAbuse(videoAbuse) 81 this.notifyModeratorsOfNewVideoAbuse(videoAbuse)
79 .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) 82 .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
80 } 83 }
81 84
82 notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { 85 notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void {
83 this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist) 86 this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist)
84 .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err })) 87 .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err }))
85 } 88 }
86 89
87 notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void { 90 notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void {
88 this.notifyVideoOwnerOfBlacklist(videoBlacklist) 91 this.notifyVideoOwnerOfBlacklist(videoBlacklist)
89 .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) 92 .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
90 } 93 }
91 94
92 notifyOnVideoUnblacklist (video: MVideoFullLight): void { 95 notifyOnVideoUnblacklist (video: MVideoFullLight): void {
@@ -96,7 +99,7 @@ class Notifier {
96 99
97 notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void { 100 notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void {
98 this.notifyOwnerVideoImportIsFinished(videoImport, success) 101 this.notifyOwnerVideoImportIsFinished(videoImport, success)
99 .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) 102 .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
100 } 103 }
101 104
102 notifyOnNewUserRegistration (user: MUserDefault): void { 105 notifyOnNewUserRegistration (user: MUserDefault): void {
@@ -106,14 +109,14 @@ class Notifier {
106 109
107 notifyOfNewUserFollow (actorFollow: MActorFollowFull): void { 110 notifyOfNewUserFollow (actorFollow: MActorFollowFull): void {
108 this.notifyUserOfNewActorFollow(actorFollow) 111 this.notifyUserOfNewActorFollow(actorFollow)
109 .catch(err => { 112 .catch(err => {
110 logger.error( 113 logger.error(
111 'Cannot notify owner of channel %s of a new follow by %s.', 114 'Cannot notify owner of channel %s of a new follow by %s.',
112 actorFollow.ActorFollowing.VideoChannel.getDisplayName(), 115 actorFollow.ActorFollowing.VideoChannel.getDisplayName(),
113 actorFollow.ActorFollower.Account.getDisplayName(), 116 actorFollow.ActorFollower.Account.getDisplayName(),
114 { err } 117 { err }
115 ) 118 )
116 }) 119 })
117 } 120 }
118 121
119 notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void { 122 notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void {
@@ -548,10 +551,10 @@ class Notifier {
548 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) 551 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
549 } 552 }
550 553
551 private async notify <T extends MUserWithNotificationSetting> (options: { 554 private async notify<T extends MUserWithNotificationSetting> (options: {
552 users: T[], 555 users: T[]
553 notificationCreator: (user: T) => Promise<UserNotificationModelForApi>, 556 notificationCreator: (user: T) => Promise<UserNotificationModelForApi>
554 emailSender: (emails: string[]) => Promise<any> | Bluebird<any>, 557 emailSender: (emails: string[]) => void
555 settingGetter: (user: T) => UserNotificationSettingValue 558 settingGetter: (user: T) => UserNotificationSettingValue
556 }) { 559 }) {
557 const emails: string[] = [] 560 const emails: string[] = []
@@ -569,7 +572,7 @@ class Notifier {
569 } 572 }
570 573
571 if (emails.length !== 0) { 574 if (emails.length !== 0) {
572 await options.emailSender(emails) 575 options.emailSender(emails)
573 } 576 }
574 } 577 }
575 578
diff --git a/server/lib/plugins/plugin-index.ts b/server/lib/plugins/plugin-index.ts
index 25b4f3c61..dcdfba28c 100644
--- a/server/lib/plugins/plugin-index.ts
+++ b/server/lib/plugins/plugin-index.ts
@@ -31,7 +31,7 @@ async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList)
31 31
32 logger.debug('Got result from PeerTube index.', { body }) 32 logger.debug('Got result from PeerTube index.', { body })
33 33
34 await addInstanceInformation(body) 34 addInstanceInformation(body)
35 35
36 return body as ResultList<PeerTubePluginIndex> 36 return body as ResultList<PeerTubePluginIndex>
37 } catch (err) { 37 } catch (err) {
@@ -40,7 +40,7 @@ async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList)
40 } 40 }
41} 41}
42 42
43async function addInstanceInformation (result: ResultList<PeerTubePluginIndex>) { 43function addInstanceInformation (result: ResultList<PeerTubePluginIndex>) {
44 for (const d of result.data) { 44 for (const d of result.data) {
45 d.installed = PluginManager.Instance.isRegistered(d.npmName) 45 d.installed = PluginManager.Instance.isRegistered(d.npmName)
46 d.name = PluginModel.normalizePluginName(d.npmName) 46 d.name = PluginModel.normalizePluginName(d.npmName)
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts
index 7ebdabd34..73f7a71ce 100644
--- a/server/lib/plugins/plugin-manager.ts
+++ b/server/lib/plugins/plugin-manager.ts
@@ -55,30 +55,30 @@ export interface HookInformationValue {
55} 55}
56 56
57type AlterableVideoConstant = 'language' | 'licence' | 'category' 57type AlterableVideoConstant = 'language' | 'licence' | 'category'
58type VideoConstant = { [ key in number | string ]: string } 58type VideoConstant = { [key in number | string]: string }
59type UpdatedVideoConstant = { 59type UpdatedVideoConstant = {
60 [ name in AlterableVideoConstant ]: { 60 [name in AlterableVideoConstant]: {
61 [ npmName: string ]: { 61 [npmName: string]: {
62 added: { key: number | string, label: string }[], 62 added: { key: number | string, label: string }[]
63 deleted: { key: number | string, label: string }[] 63 deleted: { key: number | string, label: string }[]
64 } 64 }
65 } 65 }
66} 66}
67 67
68type PluginLocalesTranslations = { 68type PluginLocalesTranslations = {
69 [ locale: string ]: PluginTranslation 69 [locale: string]: PluginTranslation
70} 70}
71 71
72export class PluginManager implements ServerHook { 72export class PluginManager implements ServerHook {
73 73
74 private static instance: PluginManager 74 private static instance: PluginManager
75 75
76 private registeredPlugins: { [ name: string ]: RegisteredPlugin } = {} 76 private registeredPlugins: { [name: string]: RegisteredPlugin } = {}
77 private settings: { [ name: string ]: RegisterServerSettingOptions[] } = {} 77 private settings: { [name: string]: RegisterServerSettingOptions[] } = {}
78 private hooks: { [ name: string ]: HookInformationValue[] } = {} 78 private hooks: { [name: string]: HookInformationValue[] } = {}
79 private translations: PluginLocalesTranslations = {} 79 private translations: PluginLocalesTranslations = {}
80 80
81 private updatedVideoConstants: UpdatedVideoConstant = { 81 private readonly updatedVideoConstants: UpdatedVideoConstant = {
82 language: {}, 82 language: {},
83 licence: {}, 83 licence: {},
84 category: {} 84 category: {}
@@ -133,7 +133,7 @@ export class PluginManager implements ServerHook {
133 133
134 // ###################### Hooks ###################### 134 // ###################### Hooks ######################
135 135
136 async runHook <T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> { 136 async runHook<T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> {
137 if (!this.hooks[hookName]) return Promise.resolve(result) 137 if (!this.hooks[hookName]) return Promise.resolve(result)
138 138
139 const hookType = getHookType(hookName) 139 const hookType = getHookType(hookName)
@@ -312,7 +312,7 @@ export class PluginManager implements ServerHook {
312 clientScripts[c.script] = c 312 clientScripts[c.script] = c
313 } 313 }
314 314
315 this.registeredPlugins[ npmName ] = { 315 this.registeredPlugins[npmName] = {
316 npmName, 316 npmName,
317 name: plugin.name, 317 name: plugin.name,
318 type: plugin.type, 318 type: plugin.type,
@@ -438,7 +438,7 @@ export class PluginManager implements ServerHook {
438 const plugins: RegisteredPlugin[] = [] 438 const plugins: RegisteredPlugin[] = []
439 439
440 for (const npmName of Object.keys(this.registeredPlugins)) { 440 for (const npmName of Object.keys(this.registeredPlugins)) {
441 const plugin = this.registeredPlugins[ npmName ] 441 const plugin = this.registeredPlugins[npmName]
442 if (plugin.type !== type) continue 442 if (plugin.type !== type) continue
443 443
444 plugins.push(plugin) 444 plugins.push(plugin)
@@ -518,11 +518,11 @@ export class PluginManager implements ServerHook {
518 } 518 }
519 } 519 }
520 520
521 private addConstant <T extends string | number> (parameters: { 521 private addConstant<T extends string | number> (parameters: {
522 npmName: string, 522 npmName: string
523 type: AlterableVideoConstant, 523 type: AlterableVideoConstant
524 obj: VideoConstant, 524 obj: VideoConstant
525 key: T, 525 key: T
526 label: string 526 label: string
527 }) { 527 }) {
528 const { npmName, type, obj, key, label } = parameters 528 const { npmName, type, obj, key, label } = parameters
@@ -545,10 +545,10 @@ export class PluginManager implements ServerHook {
545 return true 545 return true
546 } 546 }
547 547
548 private deleteConstant <T extends string | number> (parameters: { 548 private deleteConstant<T extends string | number> (parameters: {
549 npmName: string, 549 npmName: string
550 type: AlterableVideoConstant, 550 type: AlterableVideoConstant
551 obj: VideoConstant, 551 obj: VideoConstant
552 key: T 552 key: T
553 }) { 553 }) {
554 const { npmName, type, obj, key } = parameters 554 const { npmName, type, obj, key } = parameters
@@ -604,7 +604,7 @@ export class PluginManager implements ServerHook {
604 const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType) 604 const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType)
605 if (!packageJSONValid) { 605 if (!packageJSONValid) {
606 const formattedFields = badFields.map(f => `"${f}"`) 606 const formattedFields = badFields.map(f => `"${f}"`)
607 .join(', ') 607 .join(', ')
608 608
609 throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`) 609 throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`)
610 } 610 }
diff --git a/server/lib/redis.ts b/server/lib/redis.ts
index f77d0b62c..0c5dbdd3e 100644
--- a/server/lib/redis.ts
+++ b/server/lib/redis.ts
@@ -12,7 +12,7 @@ import {
12import { CONFIG } from '../initializers/config' 12import { CONFIG } from '../initializers/config'
13 13
14type CachedRoute = { 14type CachedRoute = {
15 body: string, 15 body: string
16 contentType?: string 16 contentType?: string
17 statusCode?: string 17 statusCode?: string
18} 18}
@@ -24,7 +24,8 @@ class Redis {
24 private client: RedisClient 24 private client: RedisClient
25 private prefix: string 25 private prefix: string
26 26
27 private constructor () {} 27 private constructor () {
28 }
28 29
29 init () { 30 init () {
30 // Already initialized 31 // Already initialized
@@ -49,9 +50,9 @@ class Redis {
49 return Object.assign({}, 50 return Object.assign({},
50 (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {}, 51 (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {},
51 (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {}, 52 (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {},
52 (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT) ? 53 (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT)
53 { host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT } : 54 ? { host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT }
54 { path: CONFIG.REDIS.SOCKET } 55 : { path: CONFIG.REDIS.SOCKET }
55 ) 56 )
56 } 57 }
57 58
@@ -63,7 +64,7 @@ class Redis {
63 return this.prefix 64 return this.prefix
64 } 65 }
65 66
66 /************* Forgot password *************/ 67 /* ************ Forgot password ************ */
67 68
68 async setResetPasswordVerificationString (userId: number) { 69 async setResetPasswordVerificationString (userId: number) {
69 const generatedString = await generateRandomString(32) 70 const generatedString = await generateRandomString(32)
@@ -77,7 +78,7 @@ class Redis {
77 return this.getValue(this.generateResetPasswordKey(userId)) 78 return this.getValue(this.generateResetPasswordKey(userId))
78 } 79 }
79 80
80 /************* Email verification *************/ 81 /* ************ Email verification ************ */
81 82
82 async setVerifyEmailVerificationString (userId: number) { 83 async setVerifyEmailVerificationString (userId: number) {
83 const generatedString = await generateRandomString(32) 84 const generatedString = await generateRandomString(32)
@@ -91,7 +92,7 @@ class Redis {
91 return this.getValue(this.generateVerifyEmailKey(userId)) 92 return this.getValue(this.generateVerifyEmailKey(userId))
92 } 93 }
93 94
94 /************* Contact form per IP *************/ 95 /* ************ Contact form per IP ************ */
95 96
96 async setContactFormIp (ip: string) { 97 async setContactFormIp (ip: string) {
97 return this.setValue(this.generateContactFormKey(ip), '1', CONTACT_FORM_LIFETIME) 98 return this.setValue(this.generateContactFormKey(ip), '1', CONTACT_FORM_LIFETIME)
@@ -101,7 +102,7 @@ class Redis {
101 return this.exists(this.generateContactFormKey(ip)) 102 return this.exists(this.generateContactFormKey(ip))
102 } 103 }
103 104
104 /************* Views per IP *************/ 105 /* ************ Views per IP ************ */
105 106
106 setIPVideoView (ip: string, videoUUID: string) { 107 setIPVideoView (ip: string, videoUUID: string) {
107 return this.setValue(this.generateViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME) 108 return this.setValue(this.generateViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME)
@@ -111,7 +112,7 @@ class Redis {
111 return this.exists(this.generateViewKey(ip, videoUUID)) 112 return this.exists(this.generateViewKey(ip, videoUUID))
112 } 113 }
113 114
114 /************* API cache *************/ 115 /* ************ API cache ************ */
115 116
116 async getCachedRoute (req: express.Request) { 117 async getCachedRoute (req: express.Request) {
117 const cached = await this.getObject(this.generateCachedRouteKey(req)) 118 const cached = await this.getObject(this.generateCachedRouteKey(req))
@@ -120,17 +121,17 @@ class Redis {
120 } 121 }
121 122
122 setCachedRoute (req: express.Request, body: any, lifetime: number, contentType?: string, statusCode?: number) { 123 setCachedRoute (req: express.Request, body: any, lifetime: number, contentType?: string, statusCode?: number) {
123 const cached: CachedRoute = Object.assign({}, { 124 const cached: CachedRoute = Object.assign(
124 body: body.toString() 125 {},
125 }, 126 { body: body.toString() },
126 (contentType) ? { contentType } : null, 127 (contentType) ? { contentType } : null,
127 (statusCode) ? { statusCode: statusCode.toString() } : null 128 (statusCode) ? { statusCode: statusCode.toString() } : null
128 ) 129 )
129 130
130 return this.setObject(this.generateCachedRouteKey(req), cached, lifetime) 131 return this.setObject(this.generateCachedRouteKey(req), cached, lifetime)
131 } 132 }
132 133
133 /************* Video views *************/ 134 /* ************ Video views ************ */
134 135
135 addVideoView (videoId: number) { 136 addVideoView (videoId: number) {
136 const keyIncr = this.generateVideoViewKey(videoId) 137 const keyIncr = this.generateVideoViewKey(videoId)
@@ -173,7 +174,7 @@ class Redis {
173 ]) 174 ])
174 } 175 }
175 176
176 /************* Keys generation *************/ 177 /* ************ Keys generation ************ */
177 178
178 generateCachedRouteKey (req: express.Request) { 179 generateCachedRouteKey (req: express.Request) {
179 return req.method + '-' + req.originalUrl 180 return req.method + '-' + req.originalUrl
@@ -207,7 +208,7 @@ class Redis {
207 return 'contact-form-' + ip 208 return 'contact-form-' + ip
208 } 209 }
209 210
210 /************* Redis helpers *************/ 211 /* ************ Redis helpers ************ */
211 212
212 private getValue (key: string) { 213 private getValue (key: string) {
213 return new Promise<string>((res, rej) => { 214 return new Promise<string>((res, rej) => {
@@ -265,7 +266,7 @@ class Redis {
265 }) 266 })
266 } 267 }
267 268
268 private setObject (key: string, obj: { [ id: string ]: string }, expirationMilliseconds: number) { 269 private setObject (key: string, obj: { [id: string]: string }, expirationMilliseconds: number) {
269 return new Promise<void>((res, rej) => { 270 return new Promise<void>((res, rej) => {
270 this.client.hmset(this.prefix + key, obj, (err, ok) => { 271 this.client.hmset(this.prefix + key, obj, (err, ok) => {
271 if (err) return rej(err) 272 if (err) return rej(err)
@@ -282,7 +283,7 @@ class Redis {
282 } 283 }
283 284
284 private getObject (key: string) { 285 private getObject (key: string) {
285 return new Promise<{ [ id: string ]: string }>((res, rej) => { 286 return new Promise<{ [id: string]: string }>((res, rej) => {
286 this.client.hgetall(this.prefix + key, (err, value) => { 287 this.client.hgetall(this.prefix + key, (err, value) => {
287 if (err) return rej(err) 288 if (err) return rej(err)
288 289
diff --git a/server/lib/redundancy.ts b/server/lib/redundancy.ts
index 1b4ecd7c0..78d84e02e 100644
--- a/server/lib/redundancy.ts
+++ b/server/lib/redundancy.ts
@@ -13,10 +13,10 @@ async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?
13 await videoRedundancy.destroy({ transaction: t }) 13 await videoRedundancy.destroy({ transaction: t })
14} 14}
15 15
16async function removeRedundancyOf (serverId: number) { 16async function removeRedundanciesOfServer (serverId: number) {
17 const videosRedundancy = await VideoRedundancyModel.listLocalOfServer(serverId) 17 const redundancies = await VideoRedundancyModel.listLocalOfServer(serverId)
18 18
19 for (const redundancy of videosRedundancy) { 19 for (const redundancy of redundancies) {
20 await removeVideoRedundancy(redundancy) 20 await removeVideoRedundancy(redundancy)
21 } 21 }
22} 22}
@@ -24,6 +24,6 @@ async function removeRedundancyOf (serverId: number) {
24// --------------------------------------------------------------------------- 24// ---------------------------------------------------------------------------
25 25
26export { 26export {
27 removeRedundancyOf, 27 removeRedundanciesOfServer,
28 removeVideoRedundancy 28 removeVideoRedundancy
29} 29}
diff --git a/server/lib/schedulers/auto-follow-index-instances.ts b/server/lib/schedulers/auto-follow-index-instances.ts
index dd326bc1e..d700a99f0 100644
--- a/server/lib/schedulers/auto-follow-index-instances.ts
+++ b/server/lib/schedulers/auto-follow-index-instances.ts
@@ -57,8 +57,7 @@ export class AutoFollowIndexInstances extends AbstractScheduler {
57 isAutoFollow: true 57 isAutoFollow: true
58 } 58 }
59 59
60 await JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) 60 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
61 .catch(err => logger.error('Cannot create follow job for %s.', unfollowedHost, err))
62 } 61 }
63 } 62 }
64 63
diff --git a/server/lib/schedulers/plugins-check-scheduler.ts b/server/lib/schedulers/plugins-check-scheduler.ts
index 7ff41e639..014993e94 100644
--- a/server/lib/schedulers/plugins-check-scheduler.ts
+++ b/server/lib/schedulers/plugins-check-scheduler.ts
@@ -43,7 +43,7 @@ export class PluginsCheckScheduler extends AbstractScheduler {
43 const results = await getLatestPluginsVersion(npmNames) 43 const results = await getLatestPluginsVersion(npmNames)
44 44
45 for (const result of results) { 45 for (const result of results) {
46 const plugin = pluginIndex[ result.npmName ] 46 const plugin = pluginIndex[result.npmName]
47 if (!result.latestVersion) continue 47 if (!result.latestVersion) continue
48 48
49 if ( 49 if (
diff --git a/server/lib/schedulers/remove-old-views-scheduler.ts b/server/lib/schedulers/remove-old-views-scheduler.ts
index 39fbb9163..5ae87fe50 100644
--- a/server/lib/schedulers/remove-old-views-scheduler.ts
+++ b/server/lib/schedulers/remove-old-views-scheduler.ts
@@ -1,9 +1,7 @@
1import { logger } from '../../helpers/logger' 1import { logger } from '../../helpers/logger'
2import { AbstractScheduler } from './abstract-scheduler' 2import { AbstractScheduler } from './abstract-scheduler'
3import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' 3import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
4import { UserVideoHistoryModel } from '../../models/account/user-video-history'
5import { CONFIG } from '../../initializers/config' 4import { CONFIG } from '../../initializers/config'
6import { isTestInstance } from '../../helpers/core-utils'
7import { VideoViewModel } from '../../models/video/video-views' 5import { VideoViewModel } from '../../models/video/video-views'
8 6
9export class RemoveOldViewsScheduler extends AbstractScheduler { 7export class RemoveOldViewsScheduler extends AbstractScheduler {
diff --git a/server/lib/schedulers/update-videos-scheduler.ts b/server/lib/schedulers/update-videos-scheduler.ts
index 350a335d3..956780a77 100644
--- a/server/lib/schedulers/update-videos-scheduler.ts
+++ b/server/lib/schedulers/update-videos-scheduler.ts
@@ -4,7 +4,6 @@ import { ScheduleVideoUpdateModel } from '../../models/video/schedule-video-upda
4import { retryTransactionWrapper } from '../../helpers/database-utils' 4import { retryTransactionWrapper } from '../../helpers/database-utils'
5import { federateVideoIfNeeded } from '../activitypub' 5import { federateVideoIfNeeded } from '../activitypub'
6import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' 6import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
7import { VideoPrivacy } from '../../../shared/models/videos'
8import { Notifier } from '../notifier' 7import { Notifier } from '../notifier'
9import { sequelizeTypescript } from '../../initializers/database' 8import { sequelizeTypescript } from '../../initializers/database'
10import { MVideoFullLight } from '@server/typings/models' 9import { MVideoFullLight } from '@server/typings/models'
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts
index c1c91b656..e33a4133a 100644
--- a/server/lib/schedulers/videos-redundancy-scheduler.ts
+++ b/server/lib/schedulers/videos-redundancy-scheduler.ts
@@ -1,7 +1,7 @@
1import { AbstractScheduler } from './abstract-scheduler' 1import { AbstractScheduler } from './abstract-scheduler'
2import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } from '../../initializers/constants' 2import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } from '../../initializers/constants'
3import { logger } from '../../helpers/logger' 3import { logger } from '../../helpers/logger'
4import { VideosRedundancy } from '../../../shared/models/redundancy' 4import { VideosRedundancyStrategy } from '../../../shared/models/redundancy'
5import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' 5import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
6import { downloadWebTorrentVideo, generateMagnetUri } from '../../helpers/webtorrent' 6import { downloadWebTorrentVideo, generateMagnetUri } from '../../helpers/webtorrent'
7import { join } from 'path' 7import { join } from 'path'
@@ -25,11 +25,12 @@ import {
25 MVideoWithAllFiles 25 MVideoWithAllFiles
26} from '@server/typings/models' 26} from '@server/typings/models'
27import { getVideoFilename } from '../video-paths' 27import { getVideoFilename } from '../video-paths'
28import { VideoModel } from '@server/models/video/video'
28 29
29type CandidateToDuplicate = { 30type CandidateToDuplicate = {
30 redundancy: VideosRedundancy, 31 redundancy: VideosRedundancyStrategy
31 video: MVideoWithAllFiles, 32 video: MVideoWithAllFiles
32 files: MVideoFile[], 33 files: MVideoFile[]
33 streamingPlaylists: MStreamingPlaylistFiles[] 34 streamingPlaylists: MStreamingPlaylistFiles[]
34} 35}
35 36
@@ -41,7 +42,7 @@ function isMVideoRedundancyFileVideo (
41 42
42export class VideosRedundancyScheduler extends AbstractScheduler { 43export class VideosRedundancyScheduler extends AbstractScheduler {
43 44
44 private static instance: AbstractScheduler 45 private static instance: VideosRedundancyScheduler
45 46
46 protected schedulerIntervalMs = CONFIG.REDUNDANCY.VIDEOS.CHECK_INTERVAL 47 protected schedulerIntervalMs = CONFIG.REDUNDANCY.VIDEOS.CHECK_INTERVAL
47 48
@@ -49,6 +50,22 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
49 super() 50 super()
50 } 51 }
51 52
53 async createManualRedundancy (videoId: number) {
54 const videoToDuplicate = await VideoModel.loadWithFiles(videoId)
55
56 if (!videoToDuplicate) {
57 logger.warn('Video to manually duplicate %d does not exist anymore.', videoId)
58 return
59 }
60
61 return this.createVideoRedundancies({
62 video: videoToDuplicate,
63 redundancy: null,
64 files: videoToDuplicate.VideoFiles,
65 streamingPlaylists: videoToDuplicate.VideoStreamingPlaylists
66 })
67 }
68
52 protected async internalExecute () { 69 protected async internalExecute () {
53 for (const redundancyConfig of CONFIG.REDUNDANCY.VIDEOS.STRATEGIES) { 70 for (const redundancyConfig of CONFIG.REDUNDANCY.VIDEOS.STRATEGIES) {
54 logger.info('Running redundancy scheduler for strategy %s.', redundancyConfig.strategy) 71 logger.info('Running redundancy scheduler for strategy %s.', redundancyConfig.strategy)
@@ -94,7 +111,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
94 for (const redundancyModel of expired) { 111 for (const redundancyModel of expired) {
95 try { 112 try {
96 const redundancyConfig = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) 113 const redundancyConfig = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy)
97 const candidate = { 114 const candidate: CandidateToDuplicate = {
98 redundancy: redundancyConfig, 115 redundancy: redundancyConfig,
99 video: null, 116 video: null,
100 files: [], 117 files: [],
@@ -140,7 +157,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
140 } 157 }
141 } 158 }
142 159
143 private findVideoToDuplicate (cache: VideosRedundancy) { 160 private findVideoToDuplicate (cache: VideosRedundancyStrategy) {
144 if (cache.strategy === 'most-views') { 161 if (cache.strategy === 'most-views') {
145 return VideoRedundancyModel.findMostViewToDuplicate(REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR) 162 return VideoRedundancyModel.findMostViewToDuplicate(REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR)
146 } 163 }
@@ -187,13 +204,21 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
187 } 204 }
188 } 205 }
189 206
190 private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: MVideoAccountLight, fileArg: MVideoFile) { 207 private async createVideoFileRedundancy (redundancy: VideosRedundancyStrategy | null, video: MVideoAccountLight, fileArg: MVideoFile) {
208 let strategy = 'manual'
209 let expiresOn: Date = null
210
211 if (redundancy) {
212 strategy = redundancy.strategy
213 expiresOn = this.buildNewExpiration(redundancy.minLifetime)
214 }
215
191 const file = fileArg as MVideoFileVideo 216 const file = fileArg as MVideoFileVideo
192 file.Video = video 217 file.Video = video
193 218
194 const serverActor = await getServerActor() 219 const serverActor = await getServerActor()
195 220
196 logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, redundancy.strategy) 221 logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, strategy)
197 222
198 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() 223 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
199 const magnetUri = generateMagnetUri(video, file, baseUrlHttp, baseUrlWs) 224 const magnetUri = generateMagnetUri(video, file, baseUrlHttp, baseUrlWs)
@@ -204,10 +229,10 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
204 await move(tmpPath, destPath, { overwrite: true }) 229 await move(tmpPath, destPath, { overwrite: true })
205 230
206 const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({ 231 const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({
207 expiresOn: this.buildNewExpiration(redundancy.minLifetime), 232 expiresOn,
208 url: getVideoCacheFileActivityPubUrl(file), 233 url: getVideoCacheFileActivityPubUrl(file),
209 fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), 234 fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL),
210 strategy: redundancy.strategy, 235 strategy,
211 videoFileId: file.id, 236 videoFileId: file.id,
212 actorId: serverActor.id 237 actorId: serverActor.id
213 }) 238 })
@@ -220,25 +245,33 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
220 } 245 }
221 246
222 private async createStreamingPlaylistRedundancy ( 247 private async createStreamingPlaylistRedundancy (
223 redundancy: VideosRedundancy, 248 redundancy: VideosRedundancyStrategy,
224 video: MVideoAccountLight, 249 video: MVideoAccountLight,
225 playlistArg: MStreamingPlaylist 250 playlistArg: MStreamingPlaylist
226 ) { 251 ) {
252 let strategy = 'manual'
253 let expiresOn: Date = null
254
255 if (redundancy) {
256 strategy = redundancy.strategy
257 expiresOn = this.buildNewExpiration(redundancy.minLifetime)
258 }
259
227 const playlist = playlistArg as MStreamingPlaylistVideo 260 const playlist = playlistArg as MStreamingPlaylistVideo
228 playlist.Video = video 261 playlist.Video = video
229 262
230 const serverActor = await getServerActor() 263 const serverActor = await getServerActor()
231 264
232 logger.info('Duplicating %s streaming playlist in videos redundancy with "%s" strategy.', video.url, redundancy.strategy) 265 logger.info('Duplicating %s streaming playlist in videos redundancy with "%s" strategy.', video.url, strategy)
233 266
234 const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) 267 const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid)
235 await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) 268 await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT)
236 269
237 const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({ 270 const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({
238 expiresOn: this.buildNewExpiration(redundancy.minLifetime), 271 expiresOn,
239 url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), 272 url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist),
240 fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), 273 fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL),
241 strategy: redundancy.strategy, 274 strategy,
242 videoStreamingPlaylistId: playlist.id, 275 videoStreamingPlaylistId: playlist.id,
243 actorId: serverActor.id 276 actorId: serverActor.id
244 }) 277 })
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts
index a99f71629..8dbd41771 100644
--- a/server/lib/thumbnail.ts
+++ b/server/lib/thumbnail.ts
@@ -69,7 +69,7 @@ function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile,
69function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) { 69function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) {
70 const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) 70 const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
71 71
72 const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() 72 const thumbnail = existingThumbnail || new ThumbnailModel()
73 73
74 thumbnail.filename = filename 74 thumbnail.filename = filename
75 thumbnail.height = height 75 thumbnail.height = height
@@ -142,18 +142,18 @@ function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, si
142} 142}
143 143
144async function createThumbnailFromFunction (parameters: { 144async function createThumbnailFromFunction (parameters: {
145 thumbnailCreator: () => Promise<any>, 145 thumbnailCreator: () => Promise<any>
146 filename: string, 146 filename: string
147 height: number, 147 height: number
148 width: number, 148 width: number
149 type: ThumbnailType, 149 type: ThumbnailType
150 automaticallyGenerated?: boolean, 150 automaticallyGenerated?: boolean
151 fileUrl?: string, 151 fileUrl?: string
152 existingThumbnail?: MThumbnail 152 existingThumbnail?: MThumbnail
153}) { 153}) {
154 const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters 154 const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters
155 155
156 const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() 156 const thumbnail = existingThumbnail || new ThumbnailModel()
157 157
158 thumbnail.filename = filename 158 thumbnail.filename = filename
159 thumbnail.height = height 159 thumbnail.height = height
diff --git a/server/lib/user.ts b/server/lib/user.ts
index c45438d95..88e60a7df 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -18,9 +18,9 @@ import { MUser, MUserDefault, MUserId } from '../typings/models/user'
18type ChannelNames = { name: string, displayName: string } 18type ChannelNames = { name: string, displayName: string }
19 19
20async function createUserAccountAndChannelAndPlaylist (parameters: { 20async function createUserAccountAndChannelAndPlaylist (parameters: {
21 userToCreate: MUser, 21 userToCreate: MUser
22 userDisplayName?: string, 22 userDisplayName?: string
23 channelNames?: ChannelNames, 23 channelNames?: ChannelNames
24 validateUser?: boolean 24 validateUser?: boolean
25}): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> { 25}): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> {
26 const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters 26 const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters
@@ -63,11 +63,11 @@ async function createUserAccountAndChannelAndPlaylist (parameters: {
63} 63}
64 64
65async function createLocalAccountWithoutKeys (parameters: { 65async function createLocalAccountWithoutKeys (parameters: {
66 name: string, 66 name: string
67 displayName?: string, 67 displayName?: string
68 userId: number | null, 68 userId: number | null
69 applicationId: number | null, 69 applicationId: number | null
70 t: Transaction | undefined, 70 t: Transaction | undefined
71 type?: ActivityPubActorType 71 type?: ActivityPubActorType
72}) { 72}) {
73 const { name, displayName, userId, applicationId, t, type = 'Person' } = parameters 73 const { name, displayName, userId, applicationId, t, type = 'Person' } = parameters
diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts
index 1dd45b76d..3b90b1b94 100644
--- a/server/lib/video-blacklist.ts
+++ b/server/lib/video-blacklist.ts
@@ -9,15 +9,15 @@ import { Notifier } from './notifier'
9import { MUser, MVideoBlacklistVideo, MVideoWithBlacklistLight } from '@server/typings/models' 9import { MUser, MVideoBlacklistVideo, MVideoWithBlacklistLight } from '@server/typings/models'
10 10
11async function autoBlacklistVideoIfNeeded (parameters: { 11async function autoBlacklistVideoIfNeeded (parameters: {
12 video: MVideoWithBlacklistLight, 12 video: MVideoWithBlacklistLight
13 user?: MUser, 13 user?: MUser
14 isRemote: boolean, 14 isRemote: boolean
15 isNew: boolean, 15 isNew: boolean
16 notify?: boolean, 16 notify?: boolean
17 transaction?: Transaction 17 transaction?: Transaction
18}) { 18}) {
19 const { video, user, isRemote, isNew, notify = true, transaction } = parameters 19 const { video, user, isRemote, isNew, notify = true, transaction } = parameters
20 const doAutoBlacklist = await Hooks.wrapPromiseFun( 20 const doAutoBlacklist = await Hooks.wrapFun(
21 autoBlacklistNeeded, 21 autoBlacklistNeeded,
22 { video, user, isRemote, isNew }, 22 { video, user, isRemote, isNew },
23 'filter:video.auto-blacklist.result' 23 'filter:video.auto-blacklist.result'
@@ -49,10 +49,10 @@ async function autoBlacklistVideoIfNeeded (parameters: {
49 return true 49 return true
50} 50}
51 51
52async function autoBlacklistNeeded (parameters: { 52function autoBlacklistNeeded (parameters: {
53 video: MVideoWithBlacklistLight, 53 video: MVideoWithBlacklistLight
54 isRemote: boolean, 54 isRemote: boolean
55 isNew: boolean, 55 isNew: boolean
56 user?: MUser 56 user?: MUser
57}) { 57}) {
58 const { user, video, isRemote, isNew } = parameters 58 const { user, video, isRemote, isNew } = parameters
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts
index 41eab456b..14829c9d6 100644
--- a/server/lib/video-channel.ts
+++ b/server/lib/video-channel.ts
@@ -6,8 +6,7 @@ import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUr
6import { VideoModel } from '../models/video/video' 6import { VideoModel } from '../models/video/video'
7import { MAccountId, MChannelDefault, MChannelId } from '../typings/models' 7import { MAccountId, MChannelDefault, MChannelId } from '../typings/models'
8 8
9type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault & 9type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault & { Account?: T }
10 { Account?: T }
11 10
12async function createLocalVideoChannel <T extends MAccountId> ( 11async function createLocalVideoChannel <T extends MAccountId> (
13 videoChannelInfo: VideoChannelCreate, 12 videoChannelInfo: VideoChannelCreate,
diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts
index b8074e6d2..fe83d23e7 100644
--- a/server/lib/video-comment.ts
+++ b/server/lib/video-comment.ts
@@ -7,9 +7,9 @@ import { sendCreateVideoComment } from './activitypub/send'
7import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models' 7import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models'
8 8
9async function createVideoComment (obj: { 9async function createVideoComment (obj: {
10 text: string, 10 text: string
11 inReplyToComment: MComment | null, 11 inReplyToComment: MComment | null
12 video: MVideoFullLight, 12 video: MVideoFullLight
13 account: MAccountDefault 13 account: MAccountDefault
14}, t: Sequelize.Transaction) { 14}, t: Sequelize.Transaction) {
15 let originCommentId: number | null = null 15 let originCommentId: number | null = null
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts
index c6d8466ac..ab7d04d25 100644
--- a/server/middlewares/activitypub.ts
+++ b/server/middlewares/activitypub.ts
@@ -1,10 +1,12 @@
1import { NextFunction, Request, Response } from 'express' 1import { NextFunction, Request, Response } from 'express'
2import { ActivityPubSignature } from '../../shared' 2import { ActivityDelete, ActivityPubSignature } from '../../shared'
3import { logger } from '../helpers/logger' 3import { logger } from '../helpers/logger'
4import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto' 4import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto'
5import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants' 5import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants'
6import { getOrCreateActorAndServerAndModel } from '../lib/activitypub' 6import { getOrCreateActorAndServerAndModel } from '../lib/activitypub'
7import { loadActorUrlOrGetFromWebfinger } from '../helpers/webfinger' 7import { loadActorUrlOrGetFromWebfinger } from '../helpers/webfinger'
8import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor'
9import { getAPId } from '@server/helpers/activitypub'
8 10
9async function checkSignature (req: Request, res: Response, next: NextFunction) { 11async function checkSignature (req: Request, res: Response, next: NextFunction) {
10 try { 12 try {
@@ -15,7 +17,7 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
15 17
16 // Forwarded activity 18 // Forwarded activity
17 const bodyActor = req.body.actor 19 const bodyActor = req.body.actor
18 const bodyActorId = bodyActor && bodyActor.id ? bodyActor.id : bodyActor 20 const bodyActorId = getAPId(bodyActor)
19 if (bodyActorId && bodyActorId !== actor.url) { 21 if (bodyActorId && bodyActorId !== actor.url) {
20 const jsonLDSignatureChecked = await checkJsonLDSignature(req, res) 22 const jsonLDSignatureChecked = await checkJsonLDSignature(req, res)
21 if (jsonLDSignatureChecked !== true) return 23 if (jsonLDSignatureChecked !== true) return
@@ -23,7 +25,13 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
23 25
24 return next() 26 return next()
25 } catch (err) { 27 } catch (err) {
26 logger.error('Error in ActivityPub signature checker.', err) 28 const activity: ActivityDelete = req.body
29 if (isActorDeleteActivityValid(activity) && activity.object === activity.actor) {
30 logger.debug('Handling signature error on actor delete activity', { err })
31 return res.sendStatus(204)
32 }
33
34 logger.warn('Error in ActivityPub signature checker.', { err })
27 return res.sendStatus(403) 35 return res.sendStatus(403)
28 } 36 }
29} 37}
diff --git a/server/middlewares/csp.ts b/server/middlewares/csp.ts
index d11d70790..f5de69603 100644
--- a/server/middlewares/csp.ts
+++ b/server/middlewares/csp.ts
@@ -3,20 +3,20 @@ import { CONFIG } from '../initializers/config'
3 3
4const baseDirectives = Object.assign({}, 4const baseDirectives = Object.assign({},
5 { 5 {
6 defaultSrc: ["'none'"], // by default, not specifying default-src = '*' 6 defaultSrc: [ '\'none\'' ], // by default, not specifying default-src = '*'
7 connectSrc: ['*', 'data:'], 7 connectSrc: [ '*', 'data:' ],
8 mediaSrc: ["'self'", 'https:', 'blob:'], 8 mediaSrc: [ '\'self\'', 'https:', 'blob:' ],
9 fontSrc: ["'self'", 'data:'], 9 fontSrc: [ '\'self\'', 'data:' ],
10 imgSrc: ["'self'", 'data:', 'blob:'], 10 imgSrc: [ '\'self\'', 'data:', 'blob:' ],
11 scriptSrc: ["'self' 'unsafe-inline' 'unsafe-eval'", 'blob:'], 11 scriptSrc: [ '\'self\' \'unsafe-inline\' \'unsafe-eval\'', 'blob:' ],
12 styleSrc: ["'self' 'unsafe-inline'"], 12 styleSrc: [ '\'self\' \'unsafe-inline\'' ],
13 objectSrc: ["'none'"], // only define to allow plugins, else let defaultSrc 'none' block it 13 objectSrc: [ '\'none\'' ], // only define to allow plugins, else let defaultSrc 'none' block it
14 formAction: ["'self'"], 14 formAction: [ '\'self\'' ],
15 frameAncestors: ["'none'"], 15 frameAncestors: [ '\'none\'' ],
16 baseUri: ["'self'"], 16 baseUri: [ '\'self\'' ],
17 manifestSrc: ["'self'"], 17 manifestSrc: [ '\'self\'' ],
18 frameSrc: ["'self'"], // instead of deprecated child-src / self because of test-embed 18 frameSrc: [ '\'self\'' ], // instead of deprecated child-src / self because of test-embed
19 workerSrc: ["'self'", 'blob:'] // instead of deprecated child-src 19 workerSrc: [ '\'self\'', 'blob:' ] // instead of deprecated child-src
20 }, 20 },
21 CONFIG.CSP.REPORT_URI ? { reportUri: CONFIG.CSP.REPORT_URI } : {}, 21 CONFIG.CSP.REPORT_URI ? { reportUri: CONFIG.CSP.REPORT_URI } : {},
22 CONFIG.WEBSERVER.SCHEME === 'https' ? { upgradeInsecureRequests: true } : {} 22 CONFIG.WEBSERVER.SCHEME === 'https' ? { upgradeInsecureRequests: true } : {}
@@ -29,7 +29,7 @@ const baseCSP = helmet.contentSecurityPolicy({
29}) 29})
30 30
31const embedCSP = helmet.contentSecurityPolicy({ 31const embedCSP = helmet.contentSecurityPolicy({
32 directives: Object.assign({}, baseDirectives, { frameAncestors: ['*'] }), 32 directives: Object.assign({}, baseDirectives, { frameAncestors: [ '*' ] }),
33 browserSniff: false, // assumes a modern browser, but allows CDN in front 33 browserSniff: false, // assumes a modern browser, but allows CDN in front
34 reportOnly: CONFIG.CSP.REPORT_ONLY 34 reportOnly: CONFIG.CSP.REPORT_ONLY
35}) 35})
diff --git a/server/middlewares/dnt.ts b/server/middlewares/dnt.ts
index 607def855..dd88005dd 100644
--- a/server/middlewares/dnt.ts
+++ b/server/middlewares/dnt.ts
@@ -1,6 +1,3 @@
1import * as ipaddr from 'ipaddr.js'
2import { format } from 'util'
3
4const advertiseDoNotTrack = (_, res, next) => { 1const advertiseDoNotTrack = (_, res, next) => {
5 res.setHeader('Tk', 'N') 2 res.setHeader('Tk', 'N')
6 return next() 3 return next()
diff --git a/server/middlewares/oauth.ts b/server/middlewares/oauth.ts
index 749f5cccd..9eef03bb4 100644
--- a/server/middlewares/oauth.ts
+++ b/server/middlewares/oauth.ts
@@ -51,6 +51,7 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) {
51 51
52 return next() 52 return next()
53 }) 53 })
54 .catch(err => logger.error('Cannot get access token.', { err }))
54} 55}
55 56
56function authenticatePromiseIfNeeded (req: express.Request, res: express.Response, authenticateInQuery = false) { 57function authenticatePromiseIfNeeded (req: express.Request, res: express.Response, authenticateInQuery = false) {
diff --git a/server/middlewares/sort.ts b/server/middlewares/sort.ts
index 8c27e8237..fcbb2902c 100644
--- a/server/middlewares/sort.ts
+++ b/server/middlewares/sort.ts
@@ -1,20 +1,14 @@
1import * as express from 'express' 1import * as express from 'express'
2import { SortType } from '../models/utils' 2import { SortType } from '../models/utils'
3 3
4function setDefaultSort (req: express.Request, res: express.Response, next: express.NextFunction) { 4const setDefaultSort = setDefaultSortFactory('-createdAt')
5 if (!req.query.sort) req.query.sort = '-createdAt'
6
7 return next()
8}
9 5
10function setDefaultSearchSort (req: express.Request, res: express.Response, next: express.NextFunction) { 6const setDefaultVideoRedundanciesSort = setDefaultSortFactory('name')
11 if (!req.query.sort) req.query.sort = '-match'
12 7
13 return next() 8const setDefaultSearchSort = setDefaultSortFactory('-match')
14}
15 9
16function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) { 10function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) {
17 let newSort: SortType = { sortModel: undefined, sortValue: '' } 11 const newSort: SortType = { sortModel: undefined, sortValue: '' }
18 12
19 if (!req.query.sort) req.query.sort = '-createdAt' 13 if (!req.query.sort) req.query.sort = '-createdAt'
20 14
@@ -39,5 +33,16 @@ function setBlacklistSort (req: express.Request, res: express.Response, next: ex
39export { 33export {
40 setDefaultSort, 34 setDefaultSort,
41 setDefaultSearchSort, 35 setDefaultSearchSort,
36 setDefaultVideoRedundanciesSort,
42 setBlacklistSort 37 setBlacklistSort
43} 38}
39
40// ---------------------------------------------------------------------------
41
42function setDefaultSortFactory (sort: string) {
43 return (req: express.Request, res: express.Response, next: express.NextFunction) => {
44 if (!req.query.sort) req.query.sort = sort
45
46 return next()
47 }
48}
diff --git a/server/middlewares/validators/avatar.ts b/server/middlewares/validators/avatar.ts
index 8623d07e8..2acb97483 100644
--- a/server/middlewares/validators/avatar.ts
+++ b/server/middlewares/validators/avatar.ts
@@ -8,8 +8,8 @@ import { cleanUpReqFiles } from '../../helpers/express-utils'
8 8
9const updateAvatarValidator = [ 9const updateAvatarValidator = [
10 body('avatarfile').custom((value, { req }) => isAvatarFile(req.files)).withMessage( 10 body('avatarfile').custom((value, { req }) => isAvatarFile(req.files)).withMessage(
11 'This file is not supported or too large. Please, make sure it is of the following type : ' 11 'This file is not supported or too large. Please, make sure it is of the following type : ' +
12 + CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME.join(', ') 12 CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME.join(', ')
13 ), 13 ),
14 14
15 (req: express.Request, res: express.Response, next: express.NextFunction) => { 15 (req: express.Request, res: express.Response, next: express.NextFunction) => {
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts
index 2d1f61947..ceab646c0 100644
--- a/server/middlewares/validators/config.ts
+++ b/server/middlewares/validators/config.ts
@@ -55,7 +55,7 @@ const customConfigUpdateValidator = [
55 55
56 body('theme.default').custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), 56 body('theme.default').custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'),
57 57
58 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 58 (req: express.Request, res: express.Response, next: express.NextFunction) => {
59 logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) 59 logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body })
60 60
61 if (areValidationErrors(req, res)) return 61 if (areValidationErrors(req, res)) return
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts
index 29f6c87be..f34c2b174 100644
--- a/server/middlewares/validators/feeds.ts
+++ b/server/middlewares/validators/feeds.ts
@@ -22,13 +22,13 @@ function setFeedFormatContentType (req: express.Request, res: express.Response,
22 22
23 let acceptableContentTypes: string[] 23 let acceptableContentTypes: string[]
24 if (format === 'atom' || format === 'atom1') { 24 if (format === 'atom' || format === 'atom1') {
25 acceptableContentTypes = ['application/atom+xml', 'application/xml', 'text/xml'] 25 acceptableContentTypes = [ 'application/atom+xml', 'application/xml', 'text/xml' ]
26 } else if (format === 'json' || format === 'json1') { 26 } else if (format === 'json' || format === 'json1') {
27 acceptableContentTypes = ['application/json'] 27 acceptableContentTypes = [ 'application/json' ]
28 } else if (format === 'rss' || format === 'rss2') { 28 } else if (format === 'rss' || format === 'rss2') {
29 acceptableContentTypes = ['application/rss+xml', 'application/xml', 'text/xml'] 29 acceptableContentTypes = [ 'application/rss+xml', 'application/xml', 'text/xml' ]
30 } else { 30 } else {
31 acceptableContentTypes = ['application/xml', 'text/xml'] 31 acceptableContentTypes = [ 'application/xml', 'text/xml' ]
32 } 32 }
33 33
34 if (req.accepts(acceptableContentTypes)) { 34 if (req.accepts(acceptableContentTypes)) {
diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts
index 8098e3a44..8cd3bc33d 100644
--- a/server/middlewares/validators/redundancy.ts
+++ b/server/middlewares/validators/redundancy.ts
@@ -1,12 +1,13 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { exists, isBooleanValid, isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' 3import { exists, isBooleanValid, isIdOrUUIDValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
4import { logger } from '../../helpers/logger' 4import { logger } from '../../helpers/logger'
5import { areValidationErrors } from './utils' 5import { areValidationErrors } from './utils'
6import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' 6import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
7import { isHostValid } from '../../helpers/custom-validators/servers' 7import { isHostValid } from '../../helpers/custom-validators/servers'
8import { ServerModel } from '../../models/server/server' 8import { ServerModel } from '../../models/server/server'
9import { doesVideoExist } from '../../helpers/middlewares' 9import { doesVideoExist } from '../../helpers/middlewares'
10import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies'
10 11
11const videoFileRedundancyGetValidator = [ 12const videoFileRedundancyGetValidator = [
12 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), 13 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
@@ -101,10 +102,77 @@ const updateServerRedundancyValidator = [
101 } 102 }
102] 103]
103 104
105const listVideoRedundanciesValidator = [
106 query('target')
107 .custom(isVideoRedundancyTarget).withMessage('Should have a valid video redundancies target'),
108
109 (req: express.Request, res: express.Response, next: express.NextFunction) => {
110 logger.debug('Checking listVideoRedundanciesValidator parameters', { parameters: req.query })
111
112 if (areValidationErrors(req, res)) return
113
114 return next()
115 }
116]
117
118const addVideoRedundancyValidator = [
119 body('videoId')
120 .custom(isIdValid)
121 .withMessage('Should have a valid video id'),
122
123 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
124 logger.debug('Checking addVideoRedundancyValidator parameters', { parameters: req.query })
125
126 if (areValidationErrors(req, res)) return
127
128 if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
129
130 if (res.locals.onlyVideo.remote === false) {
131 return res.status(400)
132 .json({ error: 'Cannot create a redundancy on a local video' })
133 .end()
134 }
135
136 const alreadyExists = await VideoRedundancyModel.isLocalByVideoUUIDExists(res.locals.onlyVideo.uuid)
137 if (alreadyExists) {
138 return res.status(409)
139 .json({ error: 'This video is already duplicated by your instance.' })
140 }
141
142 return next()
143 }
144]
145
146const removeVideoRedundancyValidator = [
147 param('redundancyId')
148 .custom(isIdValid)
149 .withMessage('Should have a valid redundancy id'),
150
151 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
152 logger.debug('Checking removeVideoRedundancyValidator parameters', { parameters: req.query })
153
154 if (areValidationErrors(req, res)) return
155
156 const redundancy = await VideoRedundancyModel.loadByIdWithVideo(parseInt(req.params.redundancyId, 10))
157 if (!redundancy) {
158 return res.status(404)
159 .json({ error: 'Video redundancy not found' })
160 .end()
161 }
162
163 res.locals.videoRedundancy = redundancy
164
165 return next()
166 }
167]
168
104// --------------------------------------------------------------------------- 169// ---------------------------------------------------------------------------
105 170
106export { 171export {
107 videoFileRedundancyGetValidator, 172 videoFileRedundancyGetValidator,
108 videoPlaylistRedundancyGetValidator, 173 videoPlaylistRedundancyGetValidator,
109 updateServerRedundancyValidator 174 updateServerRedundancyValidator,
175 listVideoRedundanciesValidator,
176 addVideoRedundancyValidator,
177 removeVideoRedundancyValidator
110} 178}
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts
index c75e701d6..b76dab722 100644
--- a/server/middlewares/validators/sort.ts
+++ b/server/middlewares/validators/sort.ts
@@ -23,6 +23,7 @@ const SORTABLE_USER_NOTIFICATIONS_COLUMNS = createSortableColumns(SORTABLE_COLUM
23const SORTABLE_VIDEO_PLAYLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_PLAYLISTS) 23const SORTABLE_VIDEO_PLAYLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_PLAYLISTS)
24const SORTABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.PLUGINS) 24const SORTABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.PLUGINS)
25const SORTABLE_AVAILABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) 25const SORTABLE_AVAILABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.AVAILABLE_PLUGINS)
26const SORTABLE_VIDEO_REDUNDANCIES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES)
26 27
27const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) 28const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
28const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS) 29const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS)
@@ -45,6 +46,7 @@ const userNotificationsSortValidator = checkSort(SORTABLE_USER_NOTIFICATIONS_COL
45const videoPlaylistsSortValidator = checkSort(SORTABLE_VIDEO_PLAYLISTS_COLUMNS) 46const videoPlaylistsSortValidator = checkSort(SORTABLE_VIDEO_PLAYLISTS_COLUMNS)
46const pluginsSortValidator = checkSort(SORTABLE_PLUGINS_COLUMNS) 47const pluginsSortValidator = checkSort(SORTABLE_PLUGINS_COLUMNS)
47const availablePluginsSortValidator = checkSort(SORTABLE_AVAILABLE_PLUGINS_COLUMNS) 48const availablePluginsSortValidator = checkSort(SORTABLE_AVAILABLE_PLUGINS_COLUMNS)
49const videoRedundanciesSortValidator = checkSort(SORTABLE_VIDEO_REDUNDANCIES_COLUMNS)
48 50
49// --------------------------------------------------------------------------- 51// ---------------------------------------------------------------------------
50 52
@@ -69,5 +71,6 @@ export {
69 serversBlocklistSortValidator, 71 serversBlocklistSortValidator,
70 userNotificationsSortValidator, 72 userNotificationsSortValidator,
71 videoPlaylistsSortValidator, 73 videoPlaylistsSortValidator,
74 videoRedundanciesSortValidator,
72 pluginsSortValidator 75 pluginsSortValidator
73} 76}
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index c78c67a8c..5d52b5804 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -36,7 +36,6 @@ import { doesVideoExist } from '../../helpers/middlewares'
36import { UserRole } from '../../../shared/models/users' 36import { UserRole } from '../../../shared/models/users'
37import { MUserDefault } from '@server/typings/models' 37import { MUserDefault } from '@server/typings/models'
38import { Hooks } from '@server/lib/plugins/hooks' 38import { Hooks } from '@server/lib/plugins/hooks'
39import { isLocalVideoAccepted } from '@server/lib/moderation'
40 39
41const usersAddValidator = [ 40const usersAddValidator = [
42 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), 41 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
@@ -149,7 +148,7 @@ const usersBlockingValidator = [
149] 148]
150 149
151const deleteMeValidator = [ 150const deleteMeValidator = [
152 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 151 (req: express.Request, res: express.Response, next: express.NextFunction) => {
153 const user = res.locals.oauth.token.User 152 const user = res.locals.oauth.token.User
154 if (user.username === 'root') { 153 if (user.username === 'root') {
155 return res.status(400) 154 return res.status(400)
@@ -303,7 +302,7 @@ const ensureUserRegistrationAllowed = [
303] 302]
304 303
305const ensureUserRegistrationAllowedForIP = [ 304const ensureUserRegistrationAllowedForIP = [
306 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 305 (req: express.Request, res: express.Response, next: express.NextFunction) => {
307 const allowed = isSignupAllowedForCurrentIP(req.ip) 306 const allowed = isSignupAllowedForCurrentIP(req.ip)
308 307
309 if (allowed === false) { 308 if (allowed === false) {
@@ -410,7 +409,7 @@ const userAutocompleteValidator = [
410] 409]
411 410
412const ensureAuthUserOwnsAccountValidator = [ 411const ensureAuthUserOwnsAccountValidator = [
413 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 412 (req: express.Request, res: express.Response, next: express.NextFunction) => {
414 const user = res.locals.oauth.token.User 413 const user = res.locals.oauth.token.User
415 414
416 if (res.locals.account.id !== user.Account.id) { 415 if (res.locals.account.id !== user.Account.id) {
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts
index 7b0cd6f66..872d9c2ab 100644
--- a/server/middlewares/validators/videos/video-captions.ts
+++ b/server/middlewares/validators/videos/video-captions.ts
@@ -13,10 +13,12 @@ const addVideoCaptionValidator = [
13 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), 13 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
14 param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), 14 param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'),
15 body('captionfile') 15 body('captionfile')
16 .custom((_, { req }) => isVideoCaptionFile(req.files, 'captionfile')).withMessage( 16 .custom((_, { req }) => isVideoCaptionFile(req.files, 'captionfile'))
17 `This caption file is not supported or too large. Please, make sure it is under ${CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE} and one of the following mimetypes: ` 17 .withMessage(
18 + Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT).map(key => `${key} (${MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT[key]})`).join(', ') 18 'This caption file is not supported or too large. ' +
19 ), 19 `Please, make sure it is under ${CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE} and one of the following mimetypes: ` +
20 Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT).map(key => `${key} (${MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT[key]})`).join(', ')
21 ),
20 22
21 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 23 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
22 logger.debug('Checking addVideoCaption parameters', { parameters: req.body }) 24 logger.debug('Checking addVideoCaption parameters', { parameters: req.body })
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
index 77c5f940d..da2fafb10 100644
--- a/server/middlewares/validators/videos/video-comments.ts
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -50,7 +50,7 @@ const addVideoCommentThreadValidator = [
50 if (areValidationErrors(req, res)) return 50 if (areValidationErrors(req, res)) return
51 if (!await doesVideoExist(req.params.videoId, res)) return 51 if (!await doesVideoExist(req.params.videoId, res)) return
52 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return 52 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
53 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll,false)) return 53 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return
54 54
55 return next() 55 return next()
56 } 56 }
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts
index 318dad100..5dc5db533 100644
--- a/server/middlewares/validators/videos/video-imports.ts
+++ b/server/middlewares/validators/videos/video-imports.ts
@@ -22,10 +22,11 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
22 .optional() 22 .optional()
23 .custom(isVideoMagnetUriValid).withMessage('Should have a valid video magnet URI'), 23 .custom(isVideoMagnetUriValid).withMessage('Should have a valid video magnet URI'),
24 body('torrentfile') 24 body('torrentfile')
25 .custom((value, { req }) => isVideoImportTorrentFile(req.files)).withMessage( 25 .custom((value, { req }) => isVideoImportTorrentFile(req.files))
26 'This torrent file is not supported or too large. Please, make sure it is of the following type: ' 26 .withMessage(
27 + CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.EXTNAME.join(', ') 27 'This torrent file is not supported or too large. Please, make sure it is of the following type: ' +
28 ), 28 CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.EXTNAME.join(', ')
29 ),
29 body('name') 30 body('name')
30 .optional() 31 .optional()
31 .custom(isVideoNameValid).withMessage('Should have a valid name'), 32 .custom(isVideoNameValid).withMessage('Should have a valid name'),
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index 1d67e8666..6b15c5464 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -384,10 +384,11 @@ export {
384function getCommonPlaylistEditAttributes () { 384function getCommonPlaylistEditAttributes () {
385 return [ 385 return [
386 body('thumbnailfile') 386 body('thumbnailfile')
387 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( 387 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile'))
388 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' 388 .withMessage(
389 + CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ') 389 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' +
390 ), 390 CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ')
391 ),
391 392
392 body('description') 393 body('description')
393 .optional() 394 .optional()
diff --git a/server/middlewares/validators/videos/video-rates.ts b/server/middlewares/validators/videos/video-rates.ts
index 4021cfecc..cbc144f69 100644
--- a/server/middlewares/validators/videos/video-rates.ts
+++ b/server/middlewares/validators/videos/video-rates.ts
@@ -24,7 +24,7 @@ const videoUpdateRateValidator = [
24 } 24 }
25] 25]
26 26
27const getAccountVideoRateValidator = function (rateType: VideoRateType) { 27const getAccountVideoRateValidatorFactory = function (rateType: VideoRateType) {
28 return [ 28 return [
29 param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'), 29 param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'),
30 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 30 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
@@ -51,7 +51,7 @@ const getAccountVideoRateValidator = function (rateType: VideoRateType) {
51const videoRatingValidator = [ 51const videoRatingValidator = [
52 query('rating').optional().custom(isRatingValid).withMessage('Value must be one of "like" or "dislike"'), 52 query('rating').optional().custom(isRatingValid).withMessage('Value must be one of "like" or "dislike"'),
53 53
54 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 54 (req: express.Request, res: express.Response, next: express.NextFunction) => {
55 logger.debug('Checking rating parameter', { parameters: req.params }) 55 logger.debug('Checking rating parameter', { parameters: req.params })
56 56
57 if (areValidationErrors(req, res)) return 57 if (areValidationErrors(req, res)) return
@@ -64,6 +64,6 @@ const videoRatingValidator = [
64 64
65export { 65export {
66 videoUpdateRateValidator, 66 videoUpdateRateValidator,
67 getAccountVideoRateValidator, 67 getAccountVideoRateValidatorFactory,
68 videoRatingValidator 68 videoRatingValidator
69} 69}
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 6733d9dec..a027c4840 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -49,8 +49,8 @@ import { getVideoWithAttributes } from '../../../helpers/video'
49const videosAddValidator = getCommonVideoEditAttributes().concat([ 49const videosAddValidator = getCommonVideoEditAttributes().concat([
50 body('videofile') 50 body('videofile')
51 .custom((value, { req }) => isVideoFile(req.files)).withMessage( 51 .custom((value, { req }) => isVideoFile(req.files)).withMessage(
52 'This file is not supported or too large. Please, make sure it is of the following type: ' 52 'This file is not supported or too large. Please, make sure it is of the following type: ' +
53 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') 53 CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
54 ), 54 ),
55 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), 55 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
56 body('channelId') 56 body('channelId')
@@ -147,7 +147,10 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R
147 }) 147 })
148} 148}
149 149
150const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video-with-rights', authenticateInQuery = false) => { 150const videosCustomGetValidator = (
151 fetchType: 'all' | 'only-video' | 'only-video-with-rights' | 'only-immutable-attributes',
152 authenticateInQuery = false
153) => {
151 return [ 154 return [
152 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 155 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
153 156
@@ -157,6 +160,9 @@ const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video-
157 if (areValidationErrors(req, res)) return 160 if (areValidationErrors(req, res)) return
158 if (!await doesVideoExist(req.params.id, res, fetchType)) return 161 if (!await doesVideoExist(req.params.id, res, fetchType)) return
159 162
163 // Controllers does not need to check video rights
164 if (fetchType === 'only-immutable-attributes') return next()
165
160 const video = getVideoWithAttributes(res) 166 const video = getVideoWithAttributes(res)
161 const videoAll = video as MVideoFullLight 167 const videoAll = video as MVideoFullLight
162 168
@@ -245,19 +251,15 @@ const videosTerminateChangeOwnershipValidator = [
245 // Check if the user who did the request is able to change the ownership of the video 251 // Check if the user who did the request is able to change the ownership of the video
246 if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return 252 if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return
247 253
248 return next()
249 },
250 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
251 const videoChangeOwnership = res.locals.videoChangeOwnership 254 const videoChangeOwnership = res.locals.videoChangeOwnership
252 255
253 if (videoChangeOwnership.status === VideoChangeOwnershipStatus.WAITING) { 256 if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) {
254 return next()
255 } else {
256 res.status(403) 257 res.status(403)
257 .json({ error: 'Ownership already accepted or refused' }) 258 .json({ error: 'Ownership already accepted or refused' })
258
259 return 259 return
260 } 260 }
261
262 return next()
261 } 263 }
262] 264]
263 265
@@ -284,14 +286,14 @@ function getCommonVideoEditAttributes () {
284 return [ 286 return [
285 body('thumbnailfile') 287 body('thumbnailfile')
286 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( 288 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
287 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' 289 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' +
288 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') 290 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
289 ), 291 ),
290 body('previewfile') 292 body('previewfile')
291 .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage( 293 .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
292 'This preview file is not supported or too large. Please, make sure it is of the following type: ' 294 'This preview file is not supported or too large. Please, make sure it is of the following type: ' +
293 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') 295 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
294 ), 296 ),
295 297
296 body('category') 298 body('category')
297 .optional() 299 .optional()
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts
index d50e6527f..5fe864f8b 100644
--- a/server/middlewares/validators/webfinger.ts
+++ b/server/middlewares/validators/webfinger.ts
@@ -18,15 +18,14 @@ const webfingerValidator = [
18 const nameWithHost = getHostWithPort(req.query.resource.substr(5)) 18 const nameWithHost = getHostWithPort(req.query.resource.substr(5))
19 const [ name ] = nameWithHost.split('@') 19 const [ name ] = nameWithHost.split('@')
20 20
21 // FIXME: we don't need the full actor 21 const actor = await ActorModel.loadLocalUrlByName(name)
22 const actor = await ActorModel.loadLocalByName(name)
23 if (!actor) { 22 if (!actor) {
24 return res.status(404) 23 return res.status(404)
25 .send({ error: 'Actor not found' }) 24 .send({ error: 'Actor not found' })
26 .end() 25 .end()
27 } 26 }
28 27
29 res.locals.actorFull = actor 28 res.locals.actorUrl = actor
30 return next() 29 return next()
31 } 30 }
32] 31]
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts
index 6ebe32556..e2f66d733 100644
--- a/server/models/account/account-blocklist.ts
+++ b/server/models/account/account-blocklist.ts
@@ -80,7 +80,7 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
80 attributes: [ 'accountId', 'id' ], 80 attributes: [ 'accountId', 'id' ],
81 where: { 81 where: {
82 accountId: { 82 accountId: {
83 [Op.in]: accountIds // FIXME: sequelize ANY seems broken 83 [Op.in]: accountIds
84 }, 84 },
85 targetAccountId 85 targetAccountId
86 }, 86 },
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts
index c593595b2..8aeb486d1 100644
--- a/server/models/account/account-video-rate.ts
+++ b/server/models/account/account-video-rate.ts
@@ -99,7 +99,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
99 static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, t?: Transaction): Bluebird<MAccountVideoRate> { 99 static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, t?: Transaction): Bluebird<MAccountVideoRate> {
100 const options: FindOptions = { 100 const options: FindOptions = {
101 where: { 101 where: {
102 [ Op.or]: [ 102 [Op.or]: [
103 { 103 {
104 accountId, 104 accountId,
105 videoId 105 videoId
@@ -116,10 +116,10 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
116 } 116 }
117 117
118 static listByAccountForApi (options: { 118 static listByAccountForApi (options: {
119 start: number, 119 start: number
120 count: number, 120 count: number
121 sort: string, 121 sort: string
122 type?: string, 122 type?: string
123 accountId: number 123 accountId: number
124 }) { 124 }) {
125 const query: FindOptions = { 125 const query: FindOptions = {
@@ -135,7 +135,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
135 required: true, 135 required: true,
136 include: [ 136 include: [
137 { 137 {
138 model: VideoChannelModel.scope({ method: [VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }), 138 model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }),
139 required: true 139 required: true
140 } 140 }
141 ] 141 ]
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index 8a0ffeb63..a0081f259 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -32,8 +32,9 @@ import { FindOptions, IncludeOptions, Op, Transaction, WhereOptions } from 'sequ
32import { AccountBlocklistModel } from './account-blocklist' 32import { AccountBlocklistModel } from './account-blocklist'
33import { ServerBlocklistModel } from '../server/server-blocklist' 33import { ServerBlocklistModel } from '../server/server-blocklist'
34import { ActorFollowModel } from '../activitypub/actor-follow' 34import { ActorFollowModel } from '../activitypub/actor-follow'
35import { MAccountActor, MAccountDefault, MAccountSummaryFormattable, MAccountFormattable, MAccountAP } from '../../typings/models' 35import { MAccountActor, MAccountAP, MAccountDefault, MAccountFormattable, MAccountSummaryFormattable } from '../../typings/models'
36import * as Bluebird from 'bluebird' 36import * as Bluebird from 'bluebird'
37import { ModelCache } from '@server/models/model-cache'
37 38
38export enum ScopeNames { 39export enum ScopeNames {
39 SUMMARY = 'SUMMARY' 40 SUMMARY = 'SUMMARY'
@@ -53,7 +54,7 @@ export type SummaryOptions = {
53 ] 54 ]
54})) 55}))
55@Scopes(() => ({ 56@Scopes(() => ({
56 [ ScopeNames.SUMMARY ]: (options: SummaryOptions = {}) => { 57 [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => {
57 const whereActor = options.whereActor || undefined 58 const whereActor = options.whereActor || undefined
58 59
59 const serverInclude: IncludeOptions = { 60 const serverInclude: IncludeOptions = {
@@ -218,8 +219,6 @@ export class AccountModel extends Model<AccountModel> {
218 }) 219 })
219 BlockedAccounts: AccountBlocklistModel[] 220 BlockedAccounts: AccountBlocklistModel[]
220 221
221 private static cache: { [ id: string ]: any } = {}
222
223 @BeforeDestroy 222 @BeforeDestroy
224 static async sendDeleteIfOwned (instance: AccountModel, options) { 223 static async sendDeleteIfOwned (instance: AccountModel, options) {
225 if (!instance.Actor) { 224 if (!instance.Actor) {
@@ -247,45 +246,43 @@ export class AccountModel extends Model<AccountModel> {
247 } 246 }
248 247
249 static loadLocalByName (name: string): Bluebird<MAccountDefault> { 248 static loadLocalByName (name: string): Bluebird<MAccountDefault> {
250 // The server actor never change, so we can easily cache it 249 const fun = () => {
251 if (name === SERVER_ACTOR_NAME && AccountModel.cache[name]) { 250 const query = {
252 return Bluebird.resolve(AccountModel.cache[name]) 251 where: {
253 } 252 [Op.or]: [
254 253 {
255 const query = { 254 userId: {
256 where: { 255 [Op.ne]: null
257 [ Op.or ]: [ 256 }
258 { 257 },
259 userId: { 258 {
260 [ Op.ne ]: null 259 applicationId: {
260 [Op.ne]: null
261 }
261 } 262 }
262 }, 263 ]
264 },
265 include: [
263 { 266 {
264 applicationId: { 267 model: ActorModel,
265 [ Op.ne ]: null 268 required: true,
269 where: {
270 preferredUsername: name
266 } 271 }
267 } 272 }
268 ] 273 ]
269 }, 274 }
270 include: [
271 {
272 model: ActorModel,
273 required: true,
274 where: {
275 preferredUsername: name
276 }
277 }
278 ]
279 }
280 275
281 return AccountModel.findOne(query) 276 return AccountModel.findOne(query)
282 .then(account => { 277 }
283 if (name === SERVER_ACTOR_NAME) {
284 AccountModel.cache[name] = account
285 }
286 278
287 return account 279 return ModelCache.Instance.doCache({
288 }) 280 cacheType: 'local-account-name',
281 key: name,
282 fun,
283 // The server actor never change, so we can easily cache it
284 whitelist: () => name === SERVER_ACTOR_NAME
285 })
289 } 286 }
290 287
291 static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> { 288 static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> {
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts
index a05f30175..5a725187a 100644
--- a/server/models/account/user-notification.ts
+++ b/server/models/account/user-notification.ts
@@ -363,7 +363,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
363 where: { 363 where: {
364 userId, 364 userId,
365 id: { 365 id: {
366 [Op.in]: notificationIds // FIXME: sequelize ANY seems broken 366 [Op.in]: notificationIds
367 } 367 }
368 } 368 }
369 } 369 }
@@ -379,7 +379,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
379 379
380 toFormattedJSON (this: UserNotificationModelForApi): UserNotification { 380 toFormattedJSON (this: UserNotificationModelForApi): UserNotification {
381 const video = this.Video 381 const video = this.Video
382 ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) }) 382 ? Object.assign(this.formatVideo(this.Video), { channel: this.formatActor(this.Video.VideoChannel) })
383 : undefined 383 : undefined
384 384
385 const videoImport = this.VideoImport ? { 385 const videoImport = this.VideoImport ? {
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 4c2c5e278..fb4c15aef 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -1,4 +1,4 @@
1import { FindOptions, literal, Op, QueryTypes, where, fn, col } from 'sequelize' 1import { FindOptions, literal, Op, QueryTypes, where, fn, col, WhereOptions } from 'sequelize'
2import { 2import {
3 AfterDestroy, 3 AfterDestroy,
4 AfterUpdate, 4 AfterUpdate,
@@ -101,7 +101,7 @@ enum ScopeNames {
101 required: true, 101 required: true,
102 where: { 102 where: {
103 type: { 103 type: {
104 [ Op.ne ]: VideoPlaylistType.REGULAR 104 [Op.ne]: VideoPlaylistType.REGULAR
105 } 105 }
106 } 106 }
107 } 107 }
@@ -186,7 +186,10 @@ export class UserModel extends Model<UserModel> {
186 186
187 @AllowNull(false) 187 @AllowNull(false)
188 @Default(true) 188 @Default(true)
189 @Is('UserAutoPlayNextVideoPlaylist', value => throwIfNotValid(value, isUserAutoPlayNextVideoPlaylistValid, 'auto play next video for playlists boolean')) 189 @Is(
190 'UserAutoPlayNextVideoPlaylist',
191 value => throwIfNotValid(value, isUserAutoPlayNextVideoPlaylistValid, 'auto play next video for playlists boolean')
192 )
190 @Column 193 @Column
191 autoPlayNextVideoPlaylist: boolean 194 autoPlayNextVideoPlaylist: boolean
192 195
@@ -308,7 +311,8 @@ export class UserModel extends Model<UserModel> {
308 } 311 }
309 312
310 static listForApi (start: number, count: number, sort: string, search?: string) { 313 static listForApi (start: number, count: number, sort: string, search?: string) {
311 let where = undefined 314 let where: WhereOptions
315
312 if (search) { 316 if (search) {
313 where = { 317 where = {
314 [Op.or]: [ 318 [Op.or]: [
@@ -319,7 +323,7 @@ export class UserModel extends Model<UserModel> {
319 }, 323 },
320 { 324 {
321 username: { 325 username: {
322 [ Op.iLike ]: '%' + search + '%' 326 [Op.iLike]: '%' + search + '%'
323 } 327 }
324 } 328 }
325 ] 329 ]
@@ -332,14 +336,14 @@ export class UserModel extends Model<UserModel> {
332 [ 336 [
333 literal( 337 literal(
334 '(' + 338 '(' +
335 'SELECT COALESCE(SUM("size"), 0) ' + 339 'SELECT COALESCE(SUM("size"), 0) ' +
336 'FROM (' + 340 'FROM (' +
337 'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' + 341 'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
338 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' + 342 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
339 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 343 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
340 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + 344 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
341 'WHERE "account"."userId" = "UserModel"."id" GROUP BY "video"."id"' + 345 'WHERE "account"."userId" = "UserModel"."id" GROUP BY "video"."id"' +
342 ') t' + 346 ') t' +
343 ')' 347 ')'
344 ), 348 ),
345 'videoQuotaUsed' 349 'videoQuotaUsed'
@@ -353,18 +357,18 @@ export class UserModel extends Model<UserModel> {
353 } 357 }
354 358
355 return UserModel.findAndCountAll(query) 359 return UserModel.findAndCountAll(query)
356 .then(({ rows, count }) => { 360 .then(({ rows, count }) => {
357 return { 361 return {
358 data: rows, 362 data: rows,
359 total: count 363 total: count
360 } 364 }
361 }) 365 })
362 } 366 }
363 367
364 static listWithRight (right: UserRight): Bluebird<MUserDefault[]> { 368 static listWithRight (right: UserRight): Bluebird<MUserDefault[]> {
365 const roles = Object.keys(USER_ROLE_LABELS) 369 const roles = Object.keys(USER_ROLE_LABELS)
366 .map(k => parseInt(k, 10) as UserRole) 370 .map(k => parseInt(k, 10) as UserRole)
367 .filter(role => hasUserRight(role, right)) 371 .filter(role => hasUserRight(role, right))
368 372
369 const query = { 373 const query = {
370 where: { 374 where: {
@@ -390,7 +394,7 @@ export class UserModel extends Model<UserModel> {
390 required: true, 394 required: true,
391 include: [ 395 include: [
392 { 396 {
393 attributes: [ ], 397 attributes: [],
394 model: ActorModel.unscoped(), 398 model: ActorModel.unscoped(),
395 required: true, 399 required: true,
396 where: { 400 where: {
@@ -398,7 +402,7 @@ export class UserModel extends Model<UserModel> {
398 }, 402 },
399 include: [ 403 include: [
400 { 404 {
401 attributes: [ ], 405 attributes: [],
402 as: 'ActorFollowings', 406 as: 'ActorFollowings',
403 model: ActorFollowModel.unscoped(), 407 model: ActorFollowModel.unscoped(),
404 required: true, 408 required: true,
@@ -433,7 +437,7 @@ export class UserModel extends Model<UserModel> {
433 static loadByUsername (username: string): Bluebird<MUserDefault> { 437 static loadByUsername (username: string): Bluebird<MUserDefault> {
434 const query = { 438 const query = {
435 where: { 439 where: {
436 username: { [ Op.iLike ]: username } 440 username: { [Op.iLike]: username }
437 } 441 }
438 } 442 }
439 443
@@ -443,7 +447,7 @@ export class UserModel extends Model<UserModel> {
443 static loadForMeAPI (username: string): Bluebird<MUserNotifSettingChannelDefault> { 447 static loadForMeAPI (username: string): Bluebird<MUserNotifSettingChannelDefault> {
444 const query = { 448 const query = {
445 where: { 449 where: {
446 username: { [ Op.iLike ]: username } 450 username: { [Op.iLike]: username }
447 } 451 }
448 } 452 }
449 453
@@ -465,7 +469,7 @@ export class UserModel extends Model<UserModel> {
465 469
466 const query = { 470 const query = {
467 where: { 471 where: {
468 [ Op.or ]: [ 472 [Op.or]: [
469 where(fn('lower', col('username')), fn('lower', username)), 473 where(fn('lower', col('username')), fn('lower', username)),
470 474
471 { email } 475 { email }
@@ -592,7 +596,7 @@ export class UserModel extends Model<UserModel> {
592 const query = { 596 const query = {
593 where: { 597 where: {
594 username: { 598 username: {
595 [ Op.like ]: `%${search}%` 599 [Op.like]: `%${search}%`
596 } 600 }
597 }, 601 },
598 limit: 10 602 limit: 10
@@ -652,7 +656,7 @@ export class UserModel extends Model<UserModel> {
652 videoLanguages: this.videoLanguages, 656 videoLanguages: this.videoLanguages,
653 657
654 role: this.role, 658 role: this.role,
655 roleLabel: USER_ROLE_LABELS[ this.role ], 659 roleLabel: USER_ROLE_LABELS[this.role],
656 660
657 videoQuota: this.videoQuota, 661 videoQuota: this.videoQuota,
658 videoQuotaDaily: this.videoQuotaDaily, 662 videoQuotaDaily: this.videoQuotaDaily,
@@ -686,13 +690,13 @@ export class UserModel extends Model<UserModel> {
686 690
687 if (Array.isArray(this.Account.VideoChannels) === true) { 691 if (Array.isArray(this.Account.VideoChannels) === true) {
688 json.videoChannels = this.Account.VideoChannels 692 json.videoChannels = this.Account.VideoChannels
689 .map(c => c.toFormattedJSON()) 693 .map(c => c.toFormattedJSON())
690 .sort((v1, v2) => { 694 .sort((v1, v2) => {
691 if (v1.createdAt < v2.createdAt) return -1 695 if (v1.createdAt < v2.createdAt) return -1
692 if (v1.createdAt === v2.createdAt) return 0 696 if (v1.createdAt === v2.createdAt) return 0
693 697
694 return 1 698 return 1
695 }) 699 })
696 } 700 }
697 701
698 return json 702 return json
@@ -702,7 +706,7 @@ export class UserModel extends Model<UserModel> {
702 const formatted = this.toFormattedJSON() 706 const formatted = this.toFormattedJSON()
703 707
704 const specialPlaylists = this.Account.VideoPlaylists 708 const specialPlaylists = this.Account.VideoPlaylists
705 .map(p => ({ id: p.id, name: p.name, type: p.type })) 709 .map(p => ({ id: p.id, name: p.name, type: p.type }))
706 710
707 return Object.assign(formatted, { specialPlaylists }) 711 return Object.assign(formatted, { specialPlaylists })
708 } 712 }
@@ -729,12 +733,12 @@ export class UserModel extends Model<UserModel> {
729 733
730 return 'SELECT SUM("size") AS "total" ' + 734 return 'SELECT SUM("size") AS "total" ' +
731 'FROM (' + 735 'FROM (' +
732 'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' + 736 'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
733 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' + 737 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
734 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 738 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
735 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + 739 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
736 'WHERE "account"."userId" = $userId ' + andWhere + 740 'WHERE "account"."userId" = $userId ' + andWhere +
737 'GROUP BY "video"."id"' + 741 'GROUP BY "video"."id"' +
738 ') t' 742 ') t'
739 } 743 }
740 744
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts
index f21d2b8a2..27643704e 100644
--- a/server/models/activitypub/actor-follow.ts
+++ b/server/models/activitypub/actor-follow.ts
@@ -1,5 +1,5 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { values, difference } from 'lodash' 2import { difference, values } from 'lodash'
3import { 3import {
4 AfterCreate, 4 AfterCreate,
5 AfterDestroy, 5 AfterDestroy,
@@ -23,7 +23,7 @@ import { logger } from '../../helpers/logger'
23import { getServerActor } from '../../helpers/utils' 23import { getServerActor } from '../../helpers/utils'
24import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants' 24import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants'
25import { ServerModel } from '../server/server' 25import { ServerModel } from '../server/server'
26import { createSafeIn, getSort, getFollowsSort } from '../utils' 26import { createSafeIn, getFollowsSort, getSort } from '../utils'
27import { ActorModel, unusedActorAttributesForAPI } from './actor' 27import { ActorModel, unusedActorAttributesForAPI } from './actor'
28import { VideoChannelModel } from '../video/video-channel' 28import { VideoChannelModel } from '../video/video-channel'
29import { AccountModel } from '../account/account' 29import { AccountModel } from '../account/account'
@@ -36,7 +36,6 @@ import {
36 MActorFollowSubscriptions 36 MActorFollowSubscriptions
37} from '@server/typings/models' 37} from '@server/typings/models'
38import { ActivityPubActorType } from '@shared/models' 38import { ActivityPubActorType } from '@shared/models'
39import { afterCommitIfTransaction } from '@server/helpers/database-utils'
40 39
41@Table({ 40@Table({
42 tableName: 'actorFollow', 41 tableName: 'actorFollow',
@@ -226,7 +225,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
226 225
227 return ActorFollowModel.findOne(query) 226 return ActorFollowModel.findOne(query)
228 .then(result => { 227 .then(result => {
229 if (result && result.ActorFollowing.VideoChannel) { 228 if (result?.ActorFollowing.VideoChannel) {
230 result.ActorFollowing.VideoChannel.Actor = result.ActorFollowing 229 result.ActorFollowing.VideoChannel.Actor = result.ActorFollowing
231 } 230 }
232 231
@@ -239,24 +238,24 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
239 .map(t => { 238 .map(t => {
240 if (t.host) { 239 if (t.host) {
241 return { 240 return {
242 [ Op.and ]: [ 241 [Op.and]: [
243 { 242 {
244 '$preferredUsername$': t.name 243 $preferredUsername$: t.name
245 }, 244 },
246 { 245 {
247 '$host$': t.host 246 $host$: t.host
248 } 247 }
249 ] 248 ]
250 } 249 }
251 } 250 }
252 251
253 return { 252 return {
254 [ Op.and ]: [ 253 [Op.and]: [
255 { 254 {
256 '$preferredUsername$': t.name 255 $preferredUsername$: t.name
257 }, 256 },
258 { 257 {
259 '$serverId$': null 258 $serverId$: null
260 } 259 }
261 ] 260 ]
262 } 261 }
@@ -265,9 +264,9 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
265 const query = { 264 const query = {
266 attributes: [], 265 attributes: [],
267 where: { 266 where: {
268 [ Op.and ]: [ 267 [Op.and]: [
269 { 268 {
270 [ Op.or ]: whereTab 269 [Op.or]: whereTab
271 }, 270 },
272 { 271 {
273 actorId 272 actorId
@@ -295,12 +294,12 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
295 } 294 }
296 295
297 static listFollowingForApi (options: { 296 static listFollowingForApi (options: {
298 id: number, 297 id: number
299 start: number, 298 start: number
300 count: number, 299 count: number
301 sort: string, 300 sort: string
302 state?: FollowState, 301 state?: FollowState
303 actorType?: ActivityPubActorType, 302 actorType?: ActivityPubActorType
304 search?: string 303 search?: string
305 }) { 304 }) {
306 const { id, start, count, sort, search, state, actorType } = options 305 const { id, start, count, sort, search, state, actorType } = options
@@ -312,7 +311,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
312 if (search) { 311 if (search) {
313 Object.assign(followingServerWhere, { 312 Object.assign(followingServerWhere, {
314 host: { 313 host: {
315 [ Op.iLike ]: '%' + search + '%' 314 [Op.iLike]: '%' + search + '%'
316 } 315 }
317 }) 316 })
318 } 317 }
@@ -362,12 +361,12 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
362 } 361 }
363 362
364 static listFollowersForApi (options: { 363 static listFollowersForApi (options: {
365 actorId: number, 364 actorId: number
366 start: number, 365 start: number
367 count: number, 366 count: number
368 sort: string, 367 sort: string
369 state?: FollowState, 368 state?: FollowState
370 actorType?: ActivityPubActorType, 369 actorType?: ActivityPubActorType
371 search?: string 370 search?: string
372 }) { 371 }) {
373 const { actorId, start, count, sort, search, state, actorType } = options 372 const { actorId, start, count, sort, search, state, actorType } = options
@@ -379,7 +378,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
379 if (search) { 378 if (search) {
380 Object.assign(followerServerWhere, { 379 Object.assign(followerServerWhere, {
381 host: { 380 host: {
382 [ Op.iLike ]: '%' + search + '%' 381 [Op.iLike]: '%' + search + '%'
383 } 382 }
384 }) 383 })
385 } 384 }
@@ -631,7 +630,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
631 630
632 const tasks: Bluebird<any>[] = [] 631 const tasks: Bluebird<any>[] = []
633 632
634 for (let selection of selections) { 633 for (const selection of selections) {
635 let query = 'SELECT ' + selection + ' FROM "actor" ' + 634 let query = 'SELECT ' + selection + ' FROM "actor" ' +
636 'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' + 635 'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' +
637 'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' + 636 'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' +
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts
index 007647ced..e547d2c0c 100644
--- a/server/models/activitypub/actor.ts
+++ b/server/models/activitypub/actor.ts
@@ -16,7 +16,7 @@ import {
16 Table, 16 Table,
17 UpdatedAt 17 UpdatedAt
18} from 'sequelize-typescript' 18} from 'sequelize-typescript'
19import { ActivityPubActorType } from '../../../shared/models/activitypub' 19import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub'
20import { Avatar } from '../../../shared/models/avatars/avatar.model' 20import { Avatar } from '../../../shared/models/avatars/avatar.model'
21import { activityPubContextify } from '../../helpers/activitypub' 21import { activityPubContextify } from '../../helpers/activitypub'
22import { 22import {
@@ -43,11 +43,12 @@ import {
43 MActorFull, 43 MActorFull,
44 MActorHost, 44 MActorHost,
45 MActorServer, 45 MActorServer,
46 MActorSummaryFormattable, 46 MActorSummaryFormattable, MActorUrl,
47 MActorWithInboxes 47 MActorWithInboxes
48} from '../../typings/models' 48} from '../../typings/models'
49import * as Bluebird from 'bluebird' 49import * as Bluebird from 'bluebird'
50import { Op, Transaction, literal } from 'sequelize' 50import { Op, Transaction, literal } from 'sequelize'
51import { ModelCache } from '@server/models/model-cache'
51 52
52enum ScopeNames { 53enum ScopeNames {
53 FULL = 'FULL' 54 FULL = 'FULL'
@@ -276,8 +277,6 @@ export class ActorModel extends Model<ActorModel> {
276 }) 277 })
277 VideoChannel: VideoChannelModel 278 VideoChannel: VideoChannelModel
278 279
279 private static cache: { [ id: string ]: any } = {}
280
281 static load (id: number): Bluebird<MActor> { 280 static load (id: number): Bluebird<MActor> {
282 return ActorModel.unscoped().findByPk(id) 281 return ActorModel.unscoped().findByPk(id)
283 } 282 }
@@ -334,7 +333,7 @@ export class ActorModel extends Model<ActorModel> {
334 const query = { 333 const query = {
335 where: { 334 where: {
336 followersUrl: { 335 followersUrl: {
337 [ Op.in ]: followersUrls 336 [Op.in]: followersUrls
338 } 337 }
339 }, 338 },
340 transaction 339 transaction
@@ -344,28 +343,50 @@ export class ActorModel extends Model<ActorModel> {
344 } 343 }
345 344
346 static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorFull> { 345 static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorFull> {
347 // The server actor never change, so we can easily cache it 346 const fun = () => {
348 if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.cache[preferredUsername]) { 347 const query = {
349 return Bluebird.resolve(ActorModel.cache[preferredUsername]) 348 where: {
350 } 349 preferredUsername,
350 serverId: null
351 },
352 transaction
353 }
351 354
352 const query = { 355 return ActorModel.scope(ScopeNames.FULL)
353 where: { 356 .findOne(query)
354 preferredUsername,
355 serverId: null
356 },
357 transaction
358 } 357 }
359 358
360 return ActorModel.scope(ScopeNames.FULL) 359 return ModelCache.Instance.doCache({
361 .findOne(query) 360 cacheType: 'local-actor-name',
362 .then(actor => { 361 key: preferredUsername,
363 if (preferredUsername === SERVER_ACTOR_NAME) { 362 // The server actor never change, so we can easily cache it
364 ActorModel.cache[ preferredUsername ] = actor 363 whitelist: () => preferredUsername === SERVER_ACTOR_NAME,
365 } 364 fun
365 })
366 }
366 367
367 return actor 368 static loadLocalUrlByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorUrl> {
368 }) 369 const fun = () => {
370 const query = {
371 attributes: [ 'url' ],
372 where: {
373 preferredUsername,
374 serverId: null
375 },
376 transaction
377 }
378
379 return ActorModel.unscoped()
380 .findOne(query)
381 }
382
383 return ModelCache.Instance.doCache({
384 cacheType: 'local-actor-name',
385 key: preferredUsername,
386 // The server actor never change, so we can easily cache it
387 whitelist: () => preferredUsername === SERVER_ACTOR_NAME,
388 fun
389 })
369 } 390 }
370 391
371 static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> { 392 static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> {
@@ -441,6 +462,36 @@ export class ActorModel extends Model<ActorModel> {
441 }, { where, transaction }) 462 }, { where, transaction })
442 } 463 }
443 464
465 static loadAccountActorByVideoId (videoId: number): Bluebird<MActor> {
466 const query = {
467 include: [
468 {
469 attributes: [ 'id' ],
470 model: AccountModel.unscoped(),
471 required: true,
472 include: [
473 {
474 attributes: [ 'id', 'accountId' ],
475 model: VideoChannelModel.unscoped(),
476 required: true,
477 include: [
478 {
479 attributes: [ 'id', 'channelId' ],
480 model: VideoModel.unscoped(),
481 where: {
482 id: videoId
483 }
484 }
485 ]
486 }
487 ]
488 }
489 ]
490 }
491
492 return ActorModel.unscoped().findOne(query)
493 }
494
444 getSharedInbox (this: MActorWithInboxes) { 495 getSharedInbox (this: MActorWithInboxes) {
445 return this.sharedInboxUrl || this.inboxUrl 496 return this.sharedInboxUrl || this.inboxUrl
446 } 497 }
@@ -473,9 +524,11 @@ export class ActorModel extends Model<ActorModel> {
473 } 524 }
474 525
475 toActivityPubObject (this: MActorAP, name: string) { 526 toActivityPubObject (this: MActorAP, name: string) {
476 let icon = undefined 527 let icon: ActivityIconObject
528
477 if (this.avatarId) { 529 if (this.avatarId) {
478 const extension = extname(this.Avatar.filename) 530 const extension = extname(this.Avatar.filename)
531
479 icon = { 532 icon = {
480 type: 'Image', 533 type: 'Image',
481 mediaType: extension === '.png' ? 'image/png' : 'image/jpeg', 534 mediaType: extension === '.png' ? 'image/png' : 'image/jpeg',
diff --git a/server/models/model-cache.ts b/server/models/model-cache.ts
new file mode 100644
index 000000000..a87f99aa2
--- /dev/null
+++ b/server/models/model-cache.ts
@@ -0,0 +1,91 @@
1import { Model } from 'sequelize-typescript'
2import * as Bluebird from 'bluebird'
3import { logger } from '@server/helpers/logger'
4
5type ModelCacheType =
6 'local-account-name'
7 | 'local-actor-name'
8 | 'local-actor-url'
9 | 'load-video-immutable-id'
10 | 'load-video-immutable-url'
11
12type DeleteKey =
13 'video'
14
15class ModelCache {
16
17 private static instance: ModelCache
18
19 private readonly localCache: { [id in ModelCacheType]: Map<string, any> } = {
20 'local-account-name': new Map(),
21 'local-actor-name': new Map(),
22 'local-actor-url': new Map(),
23 'load-video-immutable-id': new Map(),
24 'load-video-immutable-url': new Map()
25 }
26
27 private readonly deleteIds: {
28 [deleteKey in DeleteKey]: Map<number, { cacheType: ModelCacheType, key: string }[]>
29 } = {
30 video: new Map()
31 }
32
33 private constructor () {
34 }
35
36 static get Instance () {
37 return this.instance || (this.instance = new this())
38 }
39
40 doCache<T extends Model> (options: {
41 cacheType: ModelCacheType
42 key: string
43 fun: () => Bluebird<T>
44 whitelist?: () => boolean
45 deleteKey?: DeleteKey
46 }) {
47 const { cacheType, key, fun, whitelist, deleteKey } = options
48
49 if (whitelist && whitelist() !== true) return fun()
50
51 const cache = this.localCache[cacheType]
52
53 if (cache.has(key)) {
54 logger.debug('Model cache hit for %s -> %s.', cacheType, key)
55 return Bluebird.resolve<T>(cache.get(key))
56 }
57
58 return fun().then(m => {
59 if (!m) return m
60
61 if (!whitelist || whitelist()) cache.set(key, m)
62
63 if (deleteKey) {
64 const map = this.deleteIds[deleteKey]
65 if (!map.has(m.id)) map.set(m.id, [])
66
67 const a = map.get(m.id)
68 a.push({ cacheType, key })
69 }
70
71 return m
72 })
73 }
74
75 invalidateCache (deleteKey: DeleteKey, modelId: number) {
76 const map = this.deleteIds[deleteKey]
77
78 if (!map.has(modelId)) return
79
80 for (const toDelete of map.get(modelId)) {
81 logger.debug('Removing %s -> %d of model cache %s -> %s.', deleteKey, modelId, toDelete.cacheType, toDelete.key)
82 this.localCache[toDelete.cacheType].delete(toDelete.key)
83 }
84
85 map.delete(modelId)
86 }
87}
88
89export {
90 ModelCache
91}
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts
index b680be237..d2101ce86 100644
--- a/server/models/oauth/oauth-token.ts
+++ b/server/models/oauth/oauth-token.ts
@@ -23,10 +23,10 @@ import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
23 23
24export type OAuthTokenInfo = { 24export type OAuthTokenInfo = {
25 refreshToken: string 25 refreshToken: string
26 refreshTokenExpiresAt: Date, 26 refreshTokenExpiresAt: Date
27 client: { 27 client: {
28 id: number 28 id: number
29 }, 29 }
30 user: { 30 user: {
31 id: number 31 id: number
32 } 32 }
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts
index 8c9a7eabf..1b63d3818 100644
--- a/server/models/redundancy/video-redundancy.ts
+++ b/server/models/redundancy/video-redundancy.ts
@@ -13,13 +13,13 @@ import {
13 UpdatedAt 13 UpdatedAt
14} from 'sequelize-typescript' 14} from 'sequelize-typescript'
15import { ActorModel } from '../activitypub/actor' 15import { ActorModel } from '../activitypub/actor'
16import { getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils' 16import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils'
17import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' 17import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc'
18import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' 18import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants'
19import { VideoFileModel } from '../video/video-file' 19import { VideoFileModel } from '../video/video-file'
20import { getServerActor } from '../../helpers/utils' 20import { getServerActor } from '../../helpers/utils'
21import { VideoModel } from '../video/video' 21import { VideoModel } from '../video/video'
22import { VideoRedundancyStrategy } from '../../../shared/models/redundancy' 22import { VideoRedundancyStrategy, VideoRedundancyStrategyWithManual } from '../../../shared/models/redundancy'
23import { logger } from '../../helpers/logger' 23import { logger } from '../../helpers/logger'
24import { CacheFileObject, VideoPrivacy } from '../../../shared' 24import { CacheFileObject, VideoPrivacy } from '../../../shared'
25import { VideoChannelModel } from '../video/video-channel' 25import { VideoChannelModel } from '../video/video-channel'
@@ -27,17 +27,23 @@ import { ServerModel } from '../server/server'
27import { sample } from 'lodash' 27import { sample } from 'lodash'
28import { isTestInstance } from '../../helpers/core-utils' 28import { isTestInstance } from '../../helpers/core-utils'
29import * as Bluebird from 'bluebird' 29import * as Bluebird from 'bluebird'
30import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' 30import { col, FindOptions, fn, literal, Op, Transaction, WhereOptions } from 'sequelize'
31import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' 31import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist'
32import { CONFIG } from '../../initializers/config' 32import { CONFIG } from '../../initializers/config'
33import { MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models' 33import { MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models'
34import { VideoRedundanciesTarget } from '@shared/models/redundancy/video-redundancies-filters.model'
35import {
36 FileRedundancyInformation,
37 StreamingPlaylistRedundancyInformation,
38 VideoRedundancy
39} from '@shared/models/redundancy/video-redundancy.model'
34 40
35export enum ScopeNames { 41export enum ScopeNames {
36 WITH_VIDEO = 'WITH_VIDEO' 42 WITH_VIDEO = 'WITH_VIDEO'
37} 43}
38 44
39@Scopes(() => ({ 45@Scopes(() => ({
40 [ ScopeNames.WITH_VIDEO ]: { 46 [ScopeNames.WITH_VIDEO]: {
41 include: [ 47 include: [
42 { 48 {
43 model: VideoFileModel, 49 model: VideoFileModel,
@@ -86,7 +92,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
86 @UpdatedAt 92 @UpdatedAt
87 updatedAt: Date 93 updatedAt: Date
88 94
89 @AllowNull(false) 95 @AllowNull(true)
90 @Column 96 @Column
91 expiresOn: Date 97 expiresOn: Date
92 98
@@ -161,7 +167,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
161 logger.info('Removing duplicated video streaming playlist %s.', videoUUID) 167 logger.info('Removing duplicated video streaming playlist %s.', videoUUID)
162 168
163 videoStreamingPlaylist.Video.removeStreamingPlaylistFiles(videoStreamingPlaylist, true) 169 videoStreamingPlaylist.Video.removeStreamingPlaylistFiles(videoStreamingPlaylist, true)
164 .catch(err => logger.error('Cannot delete video streaming playlist files of %s.', videoUUID, { err })) 170 .catch(err => logger.error('Cannot delete video streaming playlist files of %s.', videoUUID, { err }))
165 } 171 }
166 172
167 return undefined 173 return undefined
@@ -193,6 +199,15 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
193 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) 199 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
194 } 200 }
195 201
202 static loadByIdWithVideo (id: number, transaction?: Transaction): Bluebird<MVideoRedundancyVideo> {
203 const query = {
204 where: { id },
205 transaction
206 }
207
208 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
209 }
210
196 static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> { 211 static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> {
197 const query = { 212 const query = {
198 where: { 213 where: {
@@ -215,12 +230,12 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
215 }, 230 },
216 include: [ 231 include: [
217 { 232 {
218 attributes: [ ], 233 attributes: [],
219 model: VideoFileModel, 234 model: VideoFileModel,
220 required: true, 235 required: true,
221 include: [ 236 include: [
222 { 237 {
223 attributes: [ ], 238 attributes: [],
224 model: VideoModel, 239 model: VideoModel,
225 required: true, 240 required: true,
226 where: { 241 where: {
@@ -233,7 +248,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
233 } 248 }
234 249
235 return VideoRedundancyModel.findOne(query) 250 return VideoRedundancyModel.findOne(query)
236 .then(r => !!r) 251 .then(r => !!r)
237 } 252 }
238 253
239 static async getVideoSample (p: Bluebird<VideoModel[]>) { 254 static async getVideoSample (p: Bluebird<VideoModel[]>) {
@@ -295,7 +310,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
295 where: { 310 where: {
296 privacy: VideoPrivacy.PUBLIC, 311 privacy: VideoPrivacy.PUBLIC,
297 views: { 312 views: {
298 [ Op.gte ]: minViews 313 [Op.gte]: minViews
299 } 314 }
300 }, 315 },
301 include: [ 316 include: [
@@ -318,7 +333,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
318 actorId: actor.id, 333 actorId: actor.id,
319 strategy, 334 strategy,
320 createdAt: { 335 createdAt: {
321 [ Op.lt ]: expiredDate 336 [Op.lt]: expiredDate
322 } 337 }
323 } 338 }
324 } 339 }
@@ -377,7 +392,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
377 where: { 392 where: {
378 actorId: actor.id, 393 actorId: actor.id,
379 expiresOn: { 394 expiresOn: {
380 [ Op.lt ]: new Date() 395 [Op.lt]: new Date()
381 } 396 }
382 } 397 }
383 } 398 }
@@ -394,7 +409,8 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
394 [Op.ne]: actor.id 409 [Op.ne]: actor.id
395 }, 410 },
396 expiresOn: { 411 expiresOn: {
397 [ Op.lt ]: new Date() 412 [Op.lt]: new Date(),
413 [Op.ne]: null
398 } 414 }
399 } 415 }
400 } 416 }
@@ -447,7 +463,112 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
447 return VideoRedundancyModel.findAll(query) 463 return VideoRedundancyModel.findAll(query)
448 } 464 }
449 465
450 static async getStats (strategy: VideoRedundancyStrategy) { 466 static listForApi (options: {
467 start: number
468 count: number
469 sort: string
470 target: VideoRedundanciesTarget
471 strategy?: string
472 }) {
473 const { start, count, sort, target, strategy } = options
474 const redundancyWhere: WhereOptions = {}
475 const videosWhere: WhereOptions = {}
476 let redundancySqlSuffix = ''
477
478 if (target === 'my-videos') {
479 Object.assign(videosWhere, { remote: false })
480 } else if (target === 'remote-videos') {
481 Object.assign(videosWhere, { remote: true })
482 Object.assign(redundancyWhere, { strategy: { [Op.ne]: null } })
483 redundancySqlSuffix = ' AND "videoRedundancy"."strategy" IS NOT NULL'
484 }
485
486 if (strategy) {
487 Object.assign(redundancyWhere, { strategy: strategy })
488 }
489
490 const videoFilterWhere = {
491 [Op.and]: [
492 {
493 [Op.or]: [
494 {
495 id: {
496 [Op.in]: literal(
497 '(' +
498 'SELECT "videoId" FROM "videoFile" ' +
499 'INNER JOIN "videoRedundancy" ON "videoRedundancy"."videoFileId" = "videoFile".id' +
500 redundancySqlSuffix +
501 ')'
502 )
503 }
504 },
505 {
506 id: {
507 [Op.in]: literal(
508 '(' +
509 'select "videoId" FROM "videoStreamingPlaylist" ' +
510 'INNER JOIN "videoRedundancy" ON "videoRedundancy"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id' +
511 redundancySqlSuffix +
512 ')'
513 )
514 }
515 }
516 ]
517 },
518
519 videosWhere
520 ]
521 }
522
523 // /!\ On video model /!\
524 const findOptions = {
525 offset: start,
526 limit: count,
527 order: getSort(sort),
528 include: [
529 {
530 required: false,
531 model: VideoFileModel.unscoped(),
532 include: [
533 {
534 model: VideoRedundancyModel.unscoped(),
535 required: false,
536 where: redundancyWhere
537 }
538 ]
539 },
540 {
541 required: false,
542 model: VideoStreamingPlaylistModel.unscoped(),
543 include: [
544 {
545 model: VideoRedundancyModel.unscoped(),
546 required: false,
547 where: redundancyWhere
548 },
549 {
550 model: VideoFileModel.unscoped(),
551 required: false
552 }
553 ]
554 }
555 ],
556 where: videoFilterWhere
557 }
558
559 // /!\ On video model /!\
560 const countOptions = {
561 where: videoFilterWhere
562 }
563
564 return Promise.all([
565 VideoModel.findAll(findOptions),
566
567 VideoModel.count(countOptions)
568 ]).then(([ data, total ]) => ({ total, data }))
569 }
570
571 static async getStats (strategy: VideoRedundancyStrategyWithManual) {
451 const actor = await getServerActor() 572 const actor = await getServerActor()
452 573
453 const query: FindOptions = { 574 const query: FindOptions = {
@@ -471,11 +592,58 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
471 } 592 }
472 593
473 return VideoRedundancyModel.findOne(query) 594 return VideoRedundancyModel.findOne(query)
474 .then((r: any) => ({ 595 .then((r: any) => ({
475 totalUsed: parseAggregateResult(r.totalUsed), 596 totalUsed: parseAggregateResult(r.totalUsed),
476 totalVideos: r.totalVideos, 597 totalVideos: r.totalVideos,
477 totalVideoFiles: r.totalVideoFiles 598 totalVideoFiles: r.totalVideoFiles
478 })) 599 }))
600 }
601
602 static toFormattedJSONStatic (video: MVideoForRedundancyAPI): VideoRedundancy {
603 const filesRedundancies: FileRedundancyInformation[] = []
604 const streamingPlaylistsRedundancies: StreamingPlaylistRedundancyInformation[] = []
605
606 for (const file of video.VideoFiles) {
607 for (const redundancy of file.RedundancyVideos) {
608 filesRedundancies.push({
609 id: redundancy.id,
610 fileUrl: redundancy.fileUrl,
611 strategy: redundancy.strategy,
612 createdAt: redundancy.createdAt,
613 updatedAt: redundancy.updatedAt,
614 expiresOn: redundancy.expiresOn,
615 size: file.size
616 })
617 }
618 }
619
620 for (const playlist of video.VideoStreamingPlaylists) {
621 const size = playlist.VideoFiles.reduce((a, b) => a + b.size, 0)
622
623 for (const redundancy of playlist.RedundancyVideos) {
624 streamingPlaylistsRedundancies.push({
625 id: redundancy.id,
626 fileUrl: redundancy.fileUrl,
627 strategy: redundancy.strategy,
628 createdAt: redundancy.createdAt,
629 updatedAt: redundancy.updatedAt,
630 expiresOn: redundancy.expiresOn,
631 size
632 })
633 }
634 }
635
636 return {
637 id: video.id,
638 name: video.name,
639 url: video.url,
640 uuid: video.uuid,
641
642 redundancies: {
643 files: filesRedundancies,
644 streamingPlaylists: streamingPlaylistsRedundancies
645 }
646 }
479 } 647 }
480 648
481 getVideo () { 649 getVideo () {
@@ -494,7 +662,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
494 id: this.url, 662 id: this.url,
495 type: 'CacheFile' as 'CacheFile', 663 type: 'CacheFile' as 'CacheFile',
496 object: this.VideoStreamingPlaylist.Video.url, 664 object: this.VideoStreamingPlaylist.Video.url,
497 expires: this.expiresOn.toISOString(), 665 expires: this.expiresOn ? this.expiresOn.toISOString() : null,
498 url: { 666 url: {
499 type: 'Link', 667 type: 'Link',
500 mediaType: 'application/x-mpegURL', 668 mediaType: 'application/x-mpegURL',
@@ -507,10 +675,10 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
507 id: this.url, 675 id: this.url,
508 type: 'CacheFile' as 'CacheFile', 676 type: 'CacheFile' as 'CacheFile',
509 object: this.VideoFile.Video.url, 677 object: this.VideoFile.Video.url,
510 expires: this.expiresOn.toISOString(), 678 expires: this.expiresOn ? this.expiresOn.toISOString() : null,
511 url: { 679 url: {
512 type: 'Link', 680 type: 'Link',
513 mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ this.VideoFile.extname ] as any, 681 mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[this.VideoFile.extname] as any,
514 href: this.fileUrl, 682 href: this.fileUrl,
515 height: this.VideoFile.resolution, 683 height: this.VideoFile.resolution,
516 size: this.VideoFile.size, 684 size: this.VideoFile.size,
@@ -525,7 +693,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
525 693
526 const notIn = literal( 694 const notIn = literal(
527 '(' + 695 '(' +
528 `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` + 696 `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` +
529 ')' 697 ')'
530 ) 698 )
531 699
@@ -535,7 +703,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
535 required: true, 703 required: true,
536 where: { 704 where: {
537 id: { 705 id: {
538 [ Op.notIn ]: notIn 706 [Op.notIn]: notIn
539 } 707 }
540 } 708 }
541 } 709 }
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts
index d094da1f5..95774a467 100644
--- a/server/models/server/plugin.ts
+++ b/server/models/server/plugin.ts
@@ -189,10 +189,10 @@ export class PluginModel extends Model<PluginModel> {
189 } 189 }
190 190
191 static listForApi (options: { 191 static listForApi (options: {
192 pluginType?: PluginType, 192 pluginType?: PluginType
193 uninstalled?: boolean, 193 uninstalled?: boolean
194 start: number, 194 start: number
195 count: number, 195 count: number
196 sort: string 196 sort: string
197 }) { 197 }) {
198 const { uninstalled = false } = options 198 const { uninstalled = false } = options
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts
index b88df4fd5..883ae47ab 100644
--- a/server/models/server/server-blocklist.ts
+++ b/server/models/server/server-blocklist.ts
@@ -81,7 +81,7 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> {
81 attributes: [ 'accountId', 'id' ], 81 attributes: [ 'accountId', 'id' ],
82 where: { 82 where: {
83 accountId: { 83 accountId: {
84 [Op.in]: accountIds // FIXME: sequelize ANY seems broken 84 [Op.in]: accountIds
85 }, 85 },
86 targetServerId 86 targetServerId
87 }, 87 },
diff --git a/server/models/utils.ts b/server/models/utils.ts
index f89b80011..f7afb8d4b 100644
--- a/server/models/utils.ts
+++ b/server/models/utils.ts
@@ -67,7 +67,7 @@ function getVideoSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): Or
67function getBlacklistSort (model: any, value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { 67function getBlacklistSort (model: any, value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
68 const [ firstSort ] = getSort(value) 68 const [ firstSort ] = getSort(value)
69 69
70 if (model) return [ [ literal(`"${model}.${firstSort[ 0 ]}" ${firstSort[ 1 ]}`) ], lastSort ] as any[] // FIXME: typings 70 if (model) return [ [ literal(`"${model}.${firstSort[0]}" ${firstSort[1]}`) ], lastSort ] as any[] // FIXME: typings
71 return [ firstSort, lastSort ] 71 return [ firstSort, lastSort ]
72} 72}
73 73
@@ -139,7 +139,7 @@ function buildServerIdsFollowedBy (actorId: any) {
139 'SELECT "actor"."serverId" FROM "actorFollow" ' + 139 'SELECT "actor"."serverId" FROM "actorFollow" ' +
140 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' + 140 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' +
141 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + 141 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
142 ')' 142 ')'
143} 143}
144 144
145function buildWhereIdOrUUID (id: number | string) { 145function buildWhereIdOrUUID (id: number | string) {
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts
index 3b011b1d2..e396784d2 100644
--- a/server/models/video/thumbnail.ts
+++ b/server/models/video/thumbnail.ts
@@ -19,6 +19,8 @@ import { CONFIG } from '../../initializers/config'
19import { VideoModel } from './video' 19import { VideoModel } from './video'
20import { VideoPlaylistModel } from './video-playlist' 20import { VideoPlaylistModel } from './video-playlist'
21import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' 21import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
22import { MVideoAccountLight } from '@server/typings/models'
23import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
22 24
23@Table({ 25@Table({
24 tableName: 'thumbnail', 26 tableName: 'thumbnail',
@@ -90,7 +92,7 @@ export class ThumbnailModel extends Model<ThumbnailModel> {
90 @UpdatedAt 92 @UpdatedAt
91 updatedAt: Date 93 updatedAt: Date
92 94
93 private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = { 95 private static readonly types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = {
94 [ThumbnailType.MINIATURE]: { 96 [ThumbnailType.MINIATURE]: {
95 label: 'miniature', 97 label: 'miniature',
96 directory: CONFIG.STORAGE.THUMBNAILS_DIR, 98 directory: CONFIG.STORAGE.THUMBNAILS_DIR,
@@ -126,11 +128,14 @@ export class ThumbnailModel extends Model<ThumbnailModel> {
126 return videoUUID + '.jpg' 128 return videoUUID + '.jpg'
127 } 129 }
128 130
129 getFileUrl (isLocal: boolean) { 131 getFileUrl (video: MVideoAccountLight) {
130 if (isLocal === false) return this.fileUrl 132 const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename
131 133
132 const staticPath = ThumbnailModel.types[this.type].staticPath 134 if (video.isOwned()) return WEBSERVER.URL + staticPath
133 return WEBSERVER.URL + staticPath + this.filename 135 if (this.fileUrl) return this.fileUrl
136
137 // Fallback if we don't have a file URL
138 return buildRemoteVideoBaseUrl(video, staticPath)
134 } 139 }
135 140
136 getPath () { 141 getPath () {
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts
index 3636db18d..da8c1577c 100644
--- a/server/models/video/video-abuse.ts
+++ b/server/models/video/video-abuse.ts
@@ -87,9 +87,9 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
87 } 87 }
88 88
89 static listForApi (parameters: { 89 static listForApi (parameters: {
90 start: number, 90 start: number
91 count: number, 91 count: number
92 sort: string, 92 sort: string
93 serverAccountId: number 93 serverAccountId: number
94 user?: MUserAccountId 94 user?: MUserAccountId
95 }) { 95 }) {
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts
index eeb2a4afd..59d3e1050 100644
--- a/server/models/video/video-caption.ts
+++ b/server/models/video/video-caption.ts
@@ -5,6 +5,7 @@ import {
5 BelongsTo, 5 BelongsTo,
6 Column, 6 Column,
7 CreatedAt, 7 CreatedAt,
8 DataType,
8 ForeignKey, 9 ForeignKey,
9 Is, 10 Is,
10 Model, 11 Model,
@@ -16,13 +17,14 @@ import { buildWhereIdOrUUID, throwIfNotValid } from '../utils'
16import { VideoModel } from './video' 17import { VideoModel } from './video'
17import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' 18import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
18import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' 19import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
19import { LAZY_STATIC_PATHS, VIDEO_LANGUAGES } from '../../initializers/constants' 20import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants'
20import { join } from 'path' 21import { join } from 'path'
21import { logger } from '../../helpers/logger' 22import { logger } from '../../helpers/logger'
22import { remove } from 'fs-extra' 23import { remove } from 'fs-extra'
23import { CONFIG } from '../../initializers/config' 24import { CONFIG } from '../../initializers/config'
24import * as Bluebird from 'bluebird' 25import * as Bluebird from 'bluebird'
25import { MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models' 26import { MVideoAccountLight, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models'
27import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
26 28
27export enum ScopeNames { 29export enum ScopeNames {
28 WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' 30 WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
@@ -64,6 +66,10 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
64 @Column 66 @Column
65 language: string 67 language: string
66 68
69 @AllowNull(true)
70 @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max))
71 fileUrl: string
72
67 @ForeignKey(() => VideoModel) 73 @ForeignKey(() => VideoModel)
68 @Column 74 @Column
69 videoId: number 75 videoId: number
@@ -114,13 +120,14 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
114 return VideoCaptionModel.findOne(query) 120 return VideoCaptionModel.findOne(query)
115 } 121 }
116 122
117 static insertOrReplaceLanguage (videoId: number, language: string, transaction: Transaction) { 123 static insertOrReplaceLanguage (videoId: number, language: string, fileUrl: string, transaction: Transaction) {
118 const values = { 124 const values = {
119 videoId, 125 videoId,
120 language 126 language,
127 fileUrl
121 } 128 }
122 129
123 return (VideoCaptionModel.upsert<VideoCaptionModel>(values, { transaction, returning: true }) as any) // FIXME: typings 130 return VideoCaptionModel.upsert(values, { transaction, returning: true })
124 .then(([ caption ]) => caption) 131 .then(([ caption ]) => caption)
125 } 132 }
126 133
@@ -175,4 +182,14 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
175 removeCaptionFile (this: MVideoCaptionFormattable) { 182 removeCaptionFile (this: MVideoCaptionFormattable) {
176 return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) 183 return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName())
177 } 184 }
185
186 getFileUrl (video: MVideoAccountLight) {
187 if (!this.Video) this.Video = video as VideoModel
188
189 if (video.isOwned()) return WEBSERVER.URL + this.getCaptionStaticPath()
190 if (this.fileUrl) return this.fileUrl
191
192 // Fallback if we don't have a file URL
193 return buildRemoteVideoBaseUrl(video, this.getCaptionStaticPath())
194 }
178} 195}
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index e10adcb3a..835216671 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -30,7 +30,7 @@ import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttr
30import { VideoModel } from './video' 30import { VideoModel } from './video'
31import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' 31import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants'
32import { ServerModel } from '../server/server' 32import { ServerModel } from '../server/server'
33import { FindOptions, ModelIndexesOptions, Op } from 'sequelize' 33import { FindOptions, Op } from 'sequelize'
34import { AvatarModel } from '../avatar/avatar' 34import { AvatarModel } from '../avatar/avatar'
35import { VideoPlaylistModel } from './video-playlist' 35import { VideoPlaylistModel } from './video-playlist'
36import * as Bluebird from 'bluebird' 36import * as Bluebird from 'bluebird'
@@ -43,18 +43,6 @@ import {
43 MChannelSummaryFormattable 43 MChannelSummaryFormattable
44} from '../../typings/models/video' 44} from '../../typings/models/video'
45 45
46// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
47const indexes: ModelIndexesOptions[] = [
48 buildTrigramSearchIndex('video_channel_name_trigram', 'name'),
49
50 {
51 fields: [ 'accountId' ]
52 },
53 {
54 fields: [ 'actorId' ]
55 }
56]
57
58export enum ScopeNames { 46export enum ScopeNames {
59 FOR_API = 'FOR_API', 47 FOR_API = 'FOR_API',
60 WITH_ACCOUNT = 'WITH_ACCOUNT', 48 WITH_ACCOUNT = 'WITH_ACCOUNT',
@@ -133,7 +121,7 @@ export type SummaryOptions = {
133 }, 121 },
134 { 122 {
135 serverId: { 123 serverId: {
136 [ Op.in ]: Sequelize.literal(inQueryInstanceFollow) 124 [Op.in]: Sequelize.literal(inQueryInstanceFollow)
137 } 125 }
138 } 126 }
139 ] 127 ]
@@ -176,7 +164,16 @@ export type SummaryOptions = {
176})) 164}))
177@Table({ 165@Table({
178 tableName: 'videoChannel', 166 tableName: 'videoChannel',
179 indexes 167 indexes: [
168 buildTrigramSearchIndex('video_channel_name_trigram', 'name'),
169
170 {
171 fields: [ 'accountId' ]
172 },
173 {
174 fields: [ 'actorId' ]
175 }
176 ]
180}) 177})
181export class VideoChannelModel extends Model<VideoChannelModel> { 178export class VideoChannelModel extends Model<VideoChannelModel> {
182 179
@@ -351,9 +348,9 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
351 } 348 }
352 349
353 static listByAccount (options: { 350 static listByAccount (options: {
354 accountId: number, 351 accountId: number
355 start: number, 352 start: number
356 count: number, 353 count: number
357 sort: string 354 sort: string
358 }) { 355 }) {
359 const query = { 356 const query = {
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts
index fb4d16b4d..b33c33d5e 100644
--- a/server/models/video/video-comment.ts
+++ b/server/models/video/video-comment.ts
@@ -257,10 +257,10 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
257 } 257 }
258 258
259 static async listThreadsForApi (parameters: { 259 static async listThreadsForApi (parameters: {
260 videoId: number, 260 videoId: number
261 start: number, 261 start: number
262 count: number, 262 count: number
263 sort: string, 263 sort: string
264 user?: MUserAccountId 264 user?: MUserAccountId
265 }) { 265 }) {
266 const { videoId, start, count, sort, user } = parameters 266 const { videoId, start, count, sort, user } = parameters
@@ -300,8 +300,8 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
300 } 300 }
301 301
302 static async listThreadCommentsForApi (parameters: { 302 static async listThreadCommentsForApi (parameters: {
303 videoId: number, 303 videoId: number
304 threadId: number, 304 threadId: number
305 user?: MUserAccountId 305 user?: MUserAccountId
306 }) { 306 }) {
307 const { videoId, threadId, user } = parameters 307 const { videoId, threadId, user } = parameters
@@ -314,7 +314,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
314 order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order, 314 order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order,
315 where: { 315 where: {
316 videoId, 316 videoId,
317 [ Op.or ]: [ 317 [Op.or]: [
318 { id: threadId }, 318 { id: threadId },
319 { originCommentId: threadId } 319 { originCommentId: threadId }
320 ], 320 ],
@@ -346,7 +346,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
346 order: [ [ 'createdAt', order ] ] as Order, 346 order: [ [ 'createdAt', order ] ] as Order,
347 where: { 347 where: {
348 id: { 348 id: {
349 [ Op.in ]: Sequelize.literal('(' + 349 [Op.in]: Sequelize.literal('(' +
350 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + 350 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' +
351 `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` + 351 `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` +
352 'UNION ' + 352 'UNION ' +
@@ -355,7 +355,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
355 ') ' + 355 ') ' +
356 'SELECT id FROM children' + 356 'SELECT id FROM children' +
357 ')'), 357 ')'),
358 [ Op.ne ]: comment.id 358 [Op.ne]: comment.id
359 } 359 }
360 }, 360 },
361 transaction: t 361 transaction: t
@@ -461,7 +461,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
461 } 461 }
462 462
463 isDeleted () { 463 isDeleted () {
464 return null !== this.deletedAt 464 return this.deletedAt !== null
465 } 465 }
466 466
467 extractMentions () { 467 extractMentions () {
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts
index 67395e5c0..1fa66fd63 100644
--- a/server/models/video/video-format-utils.ts
+++ b/server/models/video/video-format-utils.ts
@@ -27,12 +27,13 @@ import { generateMagnetUri } from '@server/helpers/webtorrent'
27export type VideoFormattingJSONOptions = { 27export type VideoFormattingJSONOptions = {
28 completeDescription?: boolean 28 completeDescription?: boolean
29 additionalAttributes: { 29 additionalAttributes: {
30 state?: boolean, 30 state?: boolean
31 waitTranscoding?: boolean, 31 waitTranscoding?: boolean
32 scheduledUpdate?: boolean, 32 scheduledUpdate?: boolean
33 blacklistInfo?: boolean 33 blacklistInfo?: boolean
34 } 34 }
35} 35}
36
36function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { 37function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video {
37 const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined 38 const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined
38 39
@@ -181,12 +182,10 @@ function videoFilesModelToFormattedJSON (
181): VideoFile[] { 182): VideoFile[] {
182 return videoFiles 183 return videoFiles
183 .map(videoFile => { 184 .map(videoFile => {
184 let resolutionLabel = videoFile.resolution + 'p'
185
186 return { 185 return {
187 resolution: { 186 resolution: {
188 id: videoFile.resolution, 187 id: videoFile.resolution,
189 label: resolutionLabel 188 label: videoFile.resolution + 'p'
190 }, 189 },
191 magnetUri: generateMagnetUri(model, videoFile, baseUrlHttp, baseUrlWs), 190 magnetUri: generateMagnetUri(model, videoFile, baseUrlHttp, baseUrlWs),
192 size: videoFile.size, 191 size: videoFile.size,
@@ -214,7 +213,7 @@ function addVideoFilesInAPAcc (
214 for (const file of files) { 213 for (const file of files) {
215 acc.push({ 214 acc.push({
216 type: 'Link', 215 type: 'Link',
217 mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ file.extname ] as any, 216 mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] as any,
218 href: model.getVideoFileUrl(file, baseUrlHttp), 217 href: model.getVideoFileUrl(file, baseUrlHttp),
219 height: file.resolution, 218 height: file.resolution,
220 size: file.size, 219 size: file.size,
@@ -282,10 +281,8 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject {
282 addVideoFilesInAPAcc(url, video, baseUrlHttp, baseUrlWs, video.VideoFiles || []) 281 addVideoFilesInAPAcc(url, video, baseUrlHttp, baseUrlWs, video.VideoFiles || [])
283 282
284 for (const playlist of (video.VideoStreamingPlaylists || [])) { 283 for (const playlist of (video.VideoStreamingPlaylists || [])) {
285 let tag: ActivityTagObject[] 284 const tag = playlist.p2pMediaLoaderInfohashes
286 285 .map(i => ({ type: 'Infohash' as 'Infohash', name: i })) as ActivityTagObject[]
287 tag = playlist.p2pMediaLoaderInfohashes
288 .map(i => ({ type: 'Infohash' as 'Infohash', name: i }))
289 tag.push({ 286 tag.push({
290 type: 'Link', 287 type: 'Link',
291 name: 'sha256', 288 name: 'sha256',
@@ -308,11 +305,12 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject {
308 for (const caption of video.VideoCaptions) { 305 for (const caption of video.VideoCaptions) {
309 subtitleLanguage.push({ 306 subtitleLanguage.push({
310 identifier: caption.language, 307 identifier: caption.language,
311 name: VideoCaptionModel.getLanguageLabel(caption.language) 308 name: VideoCaptionModel.getLanguageLabel(caption.language),
309 url: caption.getFileUrl(video)
312 }) 310 })
313 } 311 }
314 312
315 const miniature = video.getMiniature() 313 const icons = [ video.getMiniature(), video.getPreview() ]
316 314
317 return { 315 return {
318 type: 'Video' as 'Video', 316 type: 'Video' as 'Video',
@@ -337,13 +335,13 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject {
337 content: video.getTruncatedDescription(), 335 content: video.getTruncatedDescription(),
338 support: video.support, 336 support: video.support,
339 subtitleLanguage, 337 subtitleLanguage,
340 icon: { 338 icon: icons.map(i => ({
341 type: 'Image', 339 type: 'Image',
342 url: miniature.getFileUrl(video.isOwned()), 340 url: i.getFileUrl(video),
343 mediaType: 'image/jpeg', 341 mediaType: 'image/jpeg',
344 width: miniature.width, 342 width: i.width,
345 height: miniature.height 343 height: i.height
346 }, 344 })),
347 url, 345 url,
348 likes: getVideoLikesActivityPubUrl(video), 346 likes: getVideoLikesActivityPubUrl(video),
349 dislikes: getVideoDislikesActivityPubUrl(video), 347 dislikes: getVideoDislikesActivityPubUrl(video),
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts
index f2d71357f..4ba16f5fd 100644
--- a/server/models/video/video-playlist-element.ts
+++ b/server/models/video/video-playlist-element.ts
@@ -120,10 +120,10 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
120 } 120 }
121 121
122 static listForApi (options: { 122 static listForApi (options: {
123 start: number, 123 start: number
124 count: number, 124 count: number
125 videoPlaylistId: number, 125 videoPlaylistId: number
126 serverAccount: AccountModel, 126 serverAccount: AccountModel
127 user?: MUserAccountId 127 user?: MUserAccountId
128 }) { 128 }) {
129 const accountIds = [ options.serverAccount.id ] 129 const accountIds = [ options.serverAccount.id ]
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index bcdda36e5..4ca17ebec 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -68,12 +68,12 @@ type AvailableForListOptions = {
68 type?: VideoPlaylistType 68 type?: VideoPlaylistType
69 accountId?: number 69 accountId?: number
70 videoChannelId?: number 70 videoChannelId?: number
71 listMyPlaylists?: boolean, 71 listMyPlaylists?: boolean
72 search?: string 72 search?: string
73} 73}
74 74
75@Scopes(() => ({ 75@Scopes(() => ({
76 [ ScopeNames.WITH_THUMBNAIL ]: { 76 [ScopeNames.WITH_THUMBNAIL]: {
77 include: [ 77 include: [
78 { 78 {
79 model: ThumbnailModel, 79 model: ThumbnailModel,
@@ -81,7 +81,7 @@ type AvailableForListOptions = {
81 } 81 }
82 ] 82 ]
83 }, 83 },
84 [ ScopeNames.WITH_VIDEOS_LENGTH ]: { 84 [ScopeNames.WITH_VIDEOS_LENGTH]: {
85 attributes: { 85 attributes: {
86 include: [ 86 include: [
87 [ 87 [
@@ -91,7 +91,7 @@ type AvailableForListOptions = {
91 ] 91 ]
92 } 92 }
93 } as FindOptions, 93 } as FindOptions,
94 [ ScopeNames.WITH_ACCOUNT ]: { 94 [ScopeNames.WITH_ACCOUNT]: {
95 include: [ 95 include: [
96 { 96 {
97 model: AccountModel, 97 model: AccountModel,
@@ -99,7 +99,7 @@ type AvailableForListOptions = {
99 } 99 }
100 ] 100 ]
101 }, 101 },
102 [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: { 102 [ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY]: {
103 include: [ 103 include: [
104 { 104 {
105 model: AccountModel.scope(AccountScopeNames.SUMMARY), 105 model: AccountModel.scope(AccountScopeNames.SUMMARY),
@@ -111,7 +111,7 @@ type AvailableForListOptions = {
111 } 111 }
112 ] 112 ]
113 }, 113 },
114 [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: { 114 [ScopeNames.WITH_ACCOUNT_AND_CHANNEL]: {
115 include: [ 115 include: [
116 { 116 {
117 model: AccountModel, 117 model: AccountModel,
@@ -123,7 +123,7 @@ type AvailableForListOptions = {
123 } 123 }
124 ] 124 ]
125 }, 125 },
126 [ ScopeNames.AVAILABLE_FOR_LIST ]: (options: AvailableForListOptions) => { 126 [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => {
127 127
128 let whereActor: WhereOptions = {} 128 let whereActor: WhereOptions = {}
129 129
@@ -138,13 +138,13 @@ type AvailableForListOptions = {
138 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId) 138 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId)
139 139
140 whereActor = { 140 whereActor = {
141 [ Op.or ]: [ 141 [Op.or]: [
142 { 142 {
143 serverId: null 143 serverId: null
144 }, 144 },
145 { 145 {
146 serverId: { 146 serverId: {
147 [ Op.in ]: literal(inQueryInstanceFollow) 147 [Op.in]: literal(inQueryInstanceFollow)
148 } 148 }
149 } 149 }
150 ] 150 ]
@@ -172,7 +172,7 @@ type AvailableForListOptions = {
172 if (options.search) { 172 if (options.search) {
173 whereAnd.push({ 173 whereAnd.push({
174 name: { 174 name: {
175 [ Op.iLike ]: '%' + options.search + '%' 175 [Op.iLike]: '%' + options.search + '%'
176 } 176 }
177 }) 177 })
178 } 178 }
@@ -299,13 +299,13 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
299 299
300 static listForApi (options: { 300 static listForApi (options: {
301 followerActorId: number 301 followerActorId: number
302 start: number, 302 start: number
303 count: number, 303 count: number
304 sort: string, 304 sort: string
305 type?: VideoPlaylistType, 305 type?: VideoPlaylistType
306 accountId?: number, 306 accountId?: number
307 videoChannelId?: number, 307 videoChannelId?: number
308 listMyPlaylists?: boolean, 308 listMyPlaylists?: boolean
309 search?: string 309 search?: string
310 }) { 310 }) {
311 const query = { 311 const query = {
@@ -369,7 +369,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
369 model: VideoPlaylistElementModel.unscoped(), 369 model: VideoPlaylistElementModel.unscoped(),
370 where: { 370 where: {
371 videoId: { 371 videoId: {
372 [Op.in]: videoIds // FIXME: sequelize ANY seems broken 372 [Op.in]: videoIds
373 } 373 }
374 }, 374 },
375 required: true 375 required: true
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index eacffe186..5964526a9 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1,18 +1,7 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { maxBy, minBy } from 'lodash' 2import { maxBy, minBy } from 'lodash'
3import { join } from 'path' 3import { join } from 'path'
4import { 4import { CountOptions, FindOptions, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize'
5 CountOptions,
6 FindOptions,
7 IncludeOptions,
8 ModelIndexesOptions,
9 Op,
10 QueryTypes,
11 ScopeOptions,
12 Sequelize,
13 Transaction,
14 WhereOptions
15} from 'sequelize'
16import { 5import {
17 AllowNull, 6 AllowNull,
18 BeforeDestroy, 7 BeforeDestroy,
@@ -131,87 +120,19 @@ import {
131 MVideoFormattableDetails, 120 MVideoFormattableDetails,
132 MVideoForUser, 121 MVideoForUser,
133 MVideoFullLight, 122 MVideoFullLight,
134 MVideoIdThumbnail, 123 MVideoIdThumbnail, MVideoImmutable,
135 MVideoThumbnail, 124 MVideoThumbnail,
136 MVideoThumbnailBlacklist, 125 MVideoThumbnailBlacklist,
137 MVideoWithAllFiles, 126 MVideoWithAllFiles,
138 MVideoWithFile, 127 MVideoWithFile,
139 MVideoWithRights, 128 MVideoWithRights
140 MStreamingPlaylistFiles
141} from '../../typings/models' 129} from '../../typings/models'
142import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../typings/models/video/video-file' 130import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../typings/models/video/video-file'
143import { MThumbnail } from '../../typings/models/video/thumbnail' 131import { MThumbnail } from '../../typings/models/video/thumbnail'
144import { VideoFile } from '@shared/models/videos/video-file.model' 132import { VideoFile } from '@shared/models/videos/video-file.model'
145import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 133import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
146import validator from 'validator' 134import validator from 'validator'
147 135import { ModelCache } from '@server/models/model-cache'
148// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
149const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [
150 buildTrigramSearchIndex('video_name_trigram', 'name'),
151
152 { fields: [ 'createdAt' ] },
153 {
154 fields: [
155 { name: 'publishedAt', order: 'DESC' },
156 { name: 'id', order: 'ASC' }
157 ]
158 },
159 { fields: [ 'duration' ] },
160 { fields: [ 'views' ] },
161 { fields: [ 'channelId' ] },
162 {
163 fields: [ 'originallyPublishedAt' ],
164 where: {
165 originallyPublishedAt: {
166 [Op.ne]: null
167 }
168 }
169 },
170 {
171 fields: [ 'category' ], // We don't care videos with an unknown category
172 where: {
173 category: {
174 [Op.ne]: null
175 }
176 }
177 },
178 {
179 fields: [ 'licence' ], // We don't care videos with an unknown licence
180 where: {
181 licence: {
182 [Op.ne]: null
183 }
184 }
185 },
186 {
187 fields: [ 'language' ], // We don't care videos with an unknown language
188 where: {
189 language: {
190 [Op.ne]: null
191 }
192 }
193 },
194 {
195 fields: [ 'nsfw' ], // Most of the videos are not NSFW
196 where: {
197 nsfw: true
198 }
199 },
200 {
201 fields: [ 'remote' ], // Only index local videos
202 where: {
203 remote: false
204 }
205 },
206 {
207 fields: [ 'uuid' ],
208 unique: true
209 },
210 {
211 fields: [ 'url' ],
212 unique: true
213 }
214]
215 136
216export enum ScopeNames { 137export enum ScopeNames {
217 AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', 138 AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS',
@@ -224,6 +145,7 @@ export enum ScopeNames {
224 WITH_USER_HISTORY = 'WITH_USER_HISTORY', 145 WITH_USER_HISTORY = 'WITH_USER_HISTORY',
225 WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', 146 WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS',
226 WITH_USER_ID = 'WITH_USER_ID', 147 WITH_USER_ID = 'WITH_USER_ID',
148 WITH_IMMUTABLE_ATTRIBUTES = 'WITH_IMMUTABLE_ATTRIBUTES',
227 WITH_THUMBNAILS = 'WITH_THUMBNAILS' 149 WITH_THUMBNAILS = 'WITH_THUMBNAILS'
228} 150}
229 151
@@ -267,7 +189,10 @@ export type AvailableForListIDsOptions = {
267} 189}
268 190
269@Scopes(() => ({ 191@Scopes(() => ({
270 [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { 192 [ScopeNames.WITH_IMMUTABLE_ATTRIBUTES]: {
193 attributes: [ 'id', 'url', 'uuid', 'remote' ]
194 },
195 [ScopeNames.FOR_API]: (options: ForAPIOptions) => {
271 const query: FindOptions = { 196 const query: FindOptions = {
272 include: [ 197 include: [
273 { 198 {
@@ -292,7 +217,7 @@ export type AvailableForListIDsOptions = {
292 if (options.ids) { 217 if (options.ids) {
293 query.where = { 218 query.where = {
294 id: { 219 id: {
295 [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken 220 [Op.in]: options.ids
296 } 221 }
297 } 222 }
298 } 223 }
@@ -316,7 +241,7 @@ export type AvailableForListIDsOptions = {
316 241
317 return query 242 return query
318 }, 243 },
319 [ ScopeNames.AVAILABLE_FOR_LIST_IDS ]: (options: AvailableForListIDsOptions) => { 244 [ScopeNames.AVAILABLE_FOR_LIST_IDS]: (options: AvailableForListIDsOptions) => {
320 const whereAnd = options.baseWhere ? [].concat(options.baseWhere) : [] 245 const whereAnd = options.baseWhere ? [].concat(options.baseWhere) : []
321 246
322 const query: FindOptions = { 247 const query: FindOptions = {
@@ -327,11 +252,11 @@ export type AvailableForListIDsOptions = {
327 const attributesType = options.attributesType || 'id' 252 const attributesType = options.attributesType || 'id'
328 253
329 if (attributesType === 'id') query.attributes = [ 'id' ] 254 if (attributesType === 'id') query.attributes = [ 'id' ]
330 else if (attributesType === 'none') query.attributes = [ ] 255 else if (attributesType === 'none') query.attributes = []
331 256
332 whereAnd.push({ 257 whereAnd.push({
333 id: { 258 id: {
334 [ Op.notIn ]: Sequelize.literal( 259 [Op.notIn]: Sequelize.literal(
335 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' 260 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
336 ) 261 )
337 } 262 }
@@ -340,7 +265,7 @@ export type AvailableForListIDsOptions = {
340 if (options.serverAccountId) { 265 if (options.serverAccountId) {
341 whereAnd.push({ 266 whereAnd.push({
342 channelId: { 267 channelId: {
343 [ Op.notIn ]: Sequelize.literal( 268 [Op.notIn]: Sequelize.literal(
344 '(' + 269 '(' +
345 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + 270 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' +
346 buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + 271 buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) +
@@ -353,15 +278,14 @@ export type AvailableForListIDsOptions = {
353 278
354 // Only list public/published videos 279 // Only list public/published videos
355 if (!options.filter || options.filter !== 'all-local') { 280 if (!options.filter || options.filter !== 'all-local') {
356
357 const publishWhere = { 281 const publishWhere = {
358 // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding 282 // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding
359 [ Op.or ]: [ 283 [Op.or]: [
360 { 284 {
361 state: VideoState.PUBLISHED 285 state: VideoState.PUBLISHED
362 }, 286 },
363 { 287 {
364 [ Op.and ]: { 288 [Op.and]: {
365 state: VideoState.TO_TRANSCODE, 289 state: VideoState.TO_TRANSCODE,
366 waitTranscoding: false 290 waitTranscoding: false
367 } 291 }
@@ -448,7 +372,7 @@ export type AvailableForListIDsOptions = {
448 [Op.or]: [ 372 [Op.or]: [
449 { 373 {
450 id: { 374 id: {
451 [ Op.in ]: Sequelize.literal( 375 [Op.in]: Sequelize.literal(
452 '(' + 376 '(' +
453 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + 377 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' +
454 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + 378 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' +
@@ -459,7 +383,7 @@ export type AvailableForListIDsOptions = {
459 }, 383 },
460 { 384 {
461 id: { 385 id: {
462 [ Op.in ]: Sequelize.literal( 386 [Op.in]: Sequelize.literal(
463 '(' + 387 '(' +
464 'SELECT "video"."id" AS "id" FROM "video" ' + 388 'SELECT "video"."id" AS "id" FROM "video" ' +
465 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 389 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
@@ -479,7 +403,7 @@ export type AvailableForListIDsOptions = {
479 if (options.withFiles === true) { 403 if (options.withFiles === true) {
480 whereAnd.push({ 404 whereAnd.push({
481 id: { 405 id: {
482 [ Op.in ]: Sequelize.literal( 406 [Op.in]: Sequelize.literal(
483 '(SELECT "videoId" FROM "videoFile")' 407 '(SELECT "videoId" FROM "videoFile")'
484 ) 408 )
485 } 409 }
@@ -493,7 +417,7 @@ export type AvailableForListIDsOptions = {
493 417
494 whereAnd.push({ 418 whereAnd.push({
495 id: { 419 id: {
496 [ Op.in ]: Sequelize.literal( 420 [Op.in]: Sequelize.literal(
497 '(' + 421 '(' +
498 'SELECT "videoId" FROM "videoTag" ' + 422 'SELECT "videoId" FROM "videoTag" ' +
499 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 423 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
@@ -509,7 +433,7 @@ export type AvailableForListIDsOptions = {
509 433
510 whereAnd.push({ 434 whereAnd.push({
511 id: { 435 id: {
512 [ Op.in ]: Sequelize.literal( 436 [Op.in]: Sequelize.literal(
513 '(' + 437 '(' +
514 'SELECT "videoId" FROM "videoTag" ' + 438 'SELECT "videoId" FROM "videoTag" ' +
515 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 439 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
@@ -529,7 +453,7 @@ export type AvailableForListIDsOptions = {
529 if (options.categoryOneOf) { 453 if (options.categoryOneOf) {
530 whereAnd.push({ 454 whereAnd.push({
531 category: { 455 category: {
532 [ Op.or ]: options.categoryOneOf 456 [Op.or]: options.categoryOneOf
533 } 457 }
534 }) 458 })
535 } 459 }
@@ -537,7 +461,7 @@ export type AvailableForListIDsOptions = {
537 if (options.licenceOneOf) { 461 if (options.licenceOneOf) {
538 whereAnd.push({ 462 whereAnd.push({
539 licence: { 463 licence: {
540 [ Op.or ]: options.licenceOneOf 464 [Op.or]: options.licenceOneOf
541 } 465 }
542 }) 466 })
543 } 467 }
@@ -552,12 +476,12 @@ export type AvailableForListIDsOptions = {
552 [Op.or]: [ 476 [Op.or]: [
553 { 477 {
554 language: { 478 language: {
555 [ Op.or ]: videoLanguages 479 [Op.or]: videoLanguages
556 } 480 }
557 }, 481 },
558 { 482 {
559 id: { 483 id: {
560 [ Op.in ]: Sequelize.literal( 484 [Op.in]: Sequelize.literal(
561 '(' + 485 '(' +
562 'SELECT "videoId" FROM "videoCaption" ' + 486 'SELECT "videoId" FROM "videoCaption" ' +
563 'WHERE "language" IN (' + createSafeIn(VideoModel, options.languageOneOf) + ') ' + 487 'WHERE "language" IN (' + createSafeIn(VideoModel, options.languageOneOf) + ') ' +
@@ -591,12 +515,12 @@ export type AvailableForListIDsOptions = {
591 } 515 }
592 516
593 query.where = { 517 query.where = {
594 [ Op.and ]: whereAnd 518 [Op.and]: whereAnd
595 } 519 }
596 520
597 return query 521 return query
598 }, 522 },
599 [ ScopeNames.WITH_THUMBNAILS ]: { 523 [ScopeNames.WITH_THUMBNAILS]: {
600 include: [ 524 include: [
601 { 525 {
602 model: ThumbnailModel, 526 model: ThumbnailModel,
@@ -604,7 +528,7 @@ export type AvailableForListIDsOptions = {
604 } 528 }
605 ] 529 ]
606 }, 530 },
607 [ ScopeNames.WITH_USER_ID ]: { 531 [ScopeNames.WITH_USER_ID]: {
608 include: [ 532 include: [
609 { 533 {
610 attributes: [ 'accountId' ], 534 attributes: [ 'accountId' ],
@@ -620,7 +544,7 @@ export type AvailableForListIDsOptions = {
620 } 544 }
621 ] 545 ]
622 }, 546 },
623 [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { 547 [ScopeNames.WITH_ACCOUNT_DETAILS]: {
624 include: [ 548 include: [
625 { 549 {
626 model: VideoChannelModel.unscoped(), 550 model: VideoChannelModel.unscoped(),
@@ -672,10 +596,10 @@ export type AvailableForListIDsOptions = {
672 } 596 }
673 ] 597 ]
674 }, 598 },
675 [ ScopeNames.WITH_TAGS ]: { 599 [ScopeNames.WITH_TAGS]: {
676 include: [ TagModel ] 600 include: [ TagModel ]
677 }, 601 },
678 [ ScopeNames.WITH_BLACKLISTED ]: { 602 [ScopeNames.WITH_BLACKLISTED]: {
679 include: [ 603 include: [
680 { 604 {
681 attributes: [ 'id', 'reason', 'unfederated' ], 605 attributes: [ 'id', 'reason', 'unfederated' ],
@@ -684,7 +608,7 @@ export type AvailableForListIDsOptions = {
684 } 608 }
685 ] 609 ]
686 }, 610 },
687 [ ScopeNames.WITH_WEBTORRENT_FILES ]: (withRedundancies = false) => { 611 [ScopeNames.WITH_WEBTORRENT_FILES]: (withRedundancies = false) => {
688 let subInclude: any[] = [] 612 let subInclude: any[] = []
689 613
690 if (withRedundancies === true) { 614 if (withRedundancies === true) {
@@ -708,7 +632,7 @@ export type AvailableForListIDsOptions = {
708 ] 632 ]
709 } 633 }
710 }, 634 },
711 [ ScopeNames.WITH_STREAMING_PLAYLISTS ]: (withRedundancies = false) => { 635 [ScopeNames.WITH_STREAMING_PLAYLISTS]: (withRedundancies = false) => {
712 const subInclude: IncludeOptions[] = [ 636 const subInclude: IncludeOptions[] = [
713 { 637 {
714 model: VideoFileModel.unscoped(), 638 model: VideoFileModel.unscoped(),
@@ -735,7 +659,7 @@ export type AvailableForListIDsOptions = {
735 ] 659 ]
736 } 660 }
737 }, 661 },
738 [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { 662 [ScopeNames.WITH_SCHEDULED_UPDATE]: {
739 include: [ 663 include: [
740 { 664 {
741 model: ScheduleVideoUpdateModel.unscoped(), 665 model: ScheduleVideoUpdateModel.unscoped(),
@@ -743,7 +667,7 @@ export type AvailableForListIDsOptions = {
743 } 667 }
744 ] 668 ]
745 }, 669 },
746 [ ScopeNames.WITH_USER_HISTORY ]: (userId: number) => { 670 [ScopeNames.WITH_USER_HISTORY]: (userId: number) => {
747 return { 671 return {
748 include: [ 672 include: [
749 { 673 {
@@ -760,7 +684,72 @@ export type AvailableForListIDsOptions = {
760})) 684}))
761@Table({ 685@Table({
762 tableName: 'video', 686 tableName: 'video',
763 indexes 687 indexes: [
688 buildTrigramSearchIndex('video_name_trigram', 'name'),
689
690 { fields: [ 'createdAt' ] },
691 {
692 fields: [
693 { name: 'publishedAt', order: 'DESC' },
694 { name: 'id', order: 'ASC' }
695 ]
696 },
697 { fields: [ 'duration' ] },
698 { fields: [ 'views' ] },
699 { fields: [ 'channelId' ] },
700 {
701 fields: [ 'originallyPublishedAt' ],
702 where: {
703 originallyPublishedAt: {
704 [Op.ne]: null
705 }
706 }
707 },
708 {
709 fields: [ 'category' ], // We don't care videos with an unknown category
710 where: {
711 category: {
712 [Op.ne]: null
713 }
714 }
715 },
716 {
717 fields: [ 'licence' ], // We don't care videos with an unknown licence
718 where: {
719 licence: {
720 [Op.ne]: null
721 }
722 }
723 },
724 {
725 fields: [ 'language' ], // We don't care videos with an unknown language
726 where: {
727 language: {
728 [Op.ne]: null
729 }
730 }
731 },
732 {
733 fields: [ 'nsfw' ], // Most of the videos are not NSFW
734 where: {
735 nsfw: true
736 }
737 },
738 {
739 fields: [ 'remote' ], // Only index local videos
740 where: {
741 remote: false
742 }
743 },
744 {
745 fields: [ 'uuid' ],
746 unique: true
747 },
748 {
749 fields: [ 'url' ],
750 unique: true
751 }
752 ]
764}) 753})
765export class VideoModel extends Model<VideoModel> { 754export class VideoModel extends Model<VideoModel> {
766 755
@@ -1031,7 +1020,7 @@ export class VideoModel extends Model<VideoModel> {
1031 }, 1020 },
1032 onDelete: 'cascade', 1021 onDelete: 'cascade',
1033 hooks: true, 1022 hooks: true,
1034 [ 'separate' as any ]: true 1023 ['separate' as any]: true
1035 }) 1024 })
1036 VideoCaptions: VideoCaptionModel[] 1025 VideoCaptions: VideoCaptionModel[]
1037 1026
@@ -1090,6 +1079,11 @@ export class VideoModel extends Model<VideoModel> {
1090 return undefined 1079 return undefined
1091 } 1080 }
1092 1081
1082 @BeforeDestroy
1083 static invalidateCache (instance: VideoModel) {
1084 ModelCache.Instance.invalidateCache('video', instance.id)
1085 }
1086
1093 static listLocal (): Bluebird<MVideoWithAllFiles[]> { 1087 static listLocal (): Bluebird<MVideoWithAllFiles[]> {
1094 const query = { 1088 const query = {
1095 where: { 1089 where: {
@@ -1127,16 +1121,16 @@ export class VideoModel extends Model<VideoModel> {
1127 order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings 1121 order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings
1128 where: { 1122 where: {
1129 id: { 1123 id: {
1130 [ Op.in ]: Sequelize.literal('(' + rawQuery + ')') 1124 [Op.in]: Sequelize.literal('(' + rawQuery + ')')
1131 }, 1125 },
1132 [ Op.or ]: [ 1126 [Op.or]: [
1133 { privacy: VideoPrivacy.PUBLIC }, 1127 { privacy: VideoPrivacy.PUBLIC },
1134 { privacy: VideoPrivacy.UNLISTED } 1128 { privacy: VideoPrivacy.UNLISTED }
1135 ] 1129 ]
1136 }, 1130 },
1137 include: [ 1131 include: [
1138 { 1132 {
1139 attributes: [ 'language' ], 1133 attributes: [ 'language', 'fileUrl' ],
1140 model: VideoCaptionModel.unscoped(), 1134 model: VideoCaptionModel.unscoped(),
1141 required: false 1135 required: false
1142 }, 1136 },
@@ -1146,10 +1140,10 @@ export class VideoModel extends Model<VideoModel> {
1146 required: false, 1140 required: false,
1147 // We only want videos shared by this actor 1141 // We only want videos shared by this actor
1148 where: { 1142 where: {
1149 [ Op.and ]: [ 1143 [Op.and]: [
1150 { 1144 {
1151 id: { 1145 id: {
1152 [ Op.not ]: null 1146 [Op.not]: null
1153 } 1147 }
1154 }, 1148 },
1155 { 1149 {
@@ -1199,8 +1193,8 @@ export class VideoModel extends Model<VideoModel> {
1199 // totals: totalVideos + totalVideoShares 1193 // totals: totalVideos + totalVideoShares
1200 let totalVideos = 0 1194 let totalVideos = 0
1201 let totalVideoShares = 0 1195 let totalVideoShares = 0
1202 if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10) 1196 if (totals[0]) totalVideos = parseInt(totals[0].total, 10)
1203 if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10) 1197 if (totals[1]) totalVideoShares = parseInt(totals[1].total, 10)
1204 1198
1205 const total = totalVideos + totalVideoShares 1199 const total = totalVideos + totalVideoShares
1206 return { 1200 return {
@@ -1243,7 +1237,7 @@ export class VideoModel extends Model<VideoModel> {
1243 baseQuery = Object.assign(baseQuery, { 1237 baseQuery = Object.assign(baseQuery, {
1244 where: { 1238 where: {
1245 name: { 1239 name: {
1246 [ Op.iLike ]: '%' + search + '%' 1240 [Op.iLike]: '%' + search + '%'
1247 } 1241 }
1248 } 1242 }
1249 }) 1243 })
@@ -1273,25 +1267,25 @@ export class VideoModel extends Model<VideoModel> {
1273 } 1267 }
1274 1268
1275 static async listForApi (options: { 1269 static async listForApi (options: {
1276 start: number, 1270 start: number
1277 count: number, 1271 count: number
1278 sort: string, 1272 sort: string
1279 nsfw: boolean, 1273 nsfw: boolean
1280 includeLocalVideos: boolean, 1274 includeLocalVideos: boolean
1281 withFiles: boolean, 1275 withFiles: boolean
1282 categoryOneOf?: number[], 1276 categoryOneOf?: number[]
1283 licenceOneOf?: number[], 1277 licenceOneOf?: number[]
1284 languageOneOf?: string[], 1278 languageOneOf?: string[]
1285 tagsOneOf?: string[], 1279 tagsOneOf?: string[]
1286 tagsAllOf?: string[], 1280 tagsAllOf?: string[]
1287 filter?: VideoFilter, 1281 filter?: VideoFilter
1288 accountId?: number, 1282 accountId?: number
1289 videoChannelId?: number, 1283 videoChannelId?: number
1290 followerActorId?: number 1284 followerActorId?: number
1291 videoPlaylistId?: number, 1285 videoPlaylistId?: number
1292 trendingDays?: number, 1286 trendingDays?: number
1293 user?: MUserAccountId, 1287 user?: MUserAccountId
1294 historyOfUser?: MUserId, 1288 historyOfUser?: MUserId
1295 countVideos?: boolean 1289 countVideos?: boolean
1296 }) { 1290 }) {
1297 if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { 1291 if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
@@ -1357,7 +1351,7 @@ export class VideoModel extends Model<VideoModel> {
1357 tagsAllOf?: string[] 1351 tagsAllOf?: string[]
1358 durationMin?: number // seconds 1352 durationMin?: number // seconds
1359 durationMax?: number // seconds 1353 durationMax?: number // seconds
1360 user?: MUserAccountId, 1354 user?: MUserAccountId
1361 filter?: VideoFilter 1355 filter?: VideoFilter
1362 }) { 1356 }) {
1363 const whereAnd = [] 1357 const whereAnd = []
@@ -1365,8 +1359,8 @@ export class VideoModel extends Model<VideoModel> {
1365 if (options.startDate || options.endDate) { 1359 if (options.startDate || options.endDate) {
1366 const publishedAtRange = {} 1360 const publishedAtRange = {}
1367 1361
1368 if (options.startDate) publishedAtRange[ Op.gte ] = options.startDate 1362 if (options.startDate) publishedAtRange[Op.gte] = options.startDate
1369 if (options.endDate) publishedAtRange[ Op.lte ] = options.endDate 1363 if (options.endDate) publishedAtRange[Op.lte] = options.endDate
1370 1364
1371 whereAnd.push({ publishedAt: publishedAtRange }) 1365 whereAnd.push({ publishedAt: publishedAtRange })
1372 } 1366 }
@@ -1374,8 +1368,8 @@ export class VideoModel extends Model<VideoModel> {
1374 if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) { 1368 if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) {
1375 const originallyPublishedAtRange = {} 1369 const originallyPublishedAtRange = {}
1376 1370
1377 if (options.originallyPublishedStartDate) originallyPublishedAtRange[ Op.gte ] = options.originallyPublishedStartDate 1371 if (options.originallyPublishedStartDate) originallyPublishedAtRange[Op.gte] = options.originallyPublishedStartDate
1378 if (options.originallyPublishedEndDate) originallyPublishedAtRange[ Op.lte ] = options.originallyPublishedEndDate 1372 if (options.originallyPublishedEndDate) originallyPublishedAtRange[Op.lte] = options.originallyPublishedEndDate
1379 1373
1380 whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange }) 1374 whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange })
1381 } 1375 }
@@ -1383,8 +1377,8 @@ export class VideoModel extends Model<VideoModel> {
1383 if (options.durationMin || options.durationMax) { 1377 if (options.durationMin || options.durationMax) {
1384 const durationRange = {} 1378 const durationRange = {}
1385 1379
1386 if (options.durationMin) durationRange[ Op.gte ] = options.durationMin 1380 if (options.durationMin) durationRange[Op.gte] = options.durationMin
1387 if (options.durationMax) durationRange[ Op.lte ] = options.durationMax 1381 if (options.durationMax) durationRange[Op.lte] = options.durationMax
1388 1382
1389 whereAnd.push({ duration: durationRange }) 1383 whereAnd.push({ duration: durationRange })
1390 } 1384 }
@@ -1395,7 +1389,7 @@ export class VideoModel extends Model<VideoModel> {
1395 if (options.search) { 1389 if (options.search) {
1396 const trigramSearch = { 1390 const trigramSearch = {
1397 id: { 1391 id: {
1398 [ Op.in ]: Sequelize.literal( 1392 [Op.in]: Sequelize.literal(
1399 '(' + 1393 '(' +
1400 'SELECT "video"."id" FROM "video" ' + 1394 'SELECT "video"."id" FROM "video" ' +
1401 'WHERE ' + 1395 'WHERE ' +
@@ -1484,6 +1478,24 @@ export class VideoModel extends Model<VideoModel> {
1484 ]).findOne(options) 1478 ]).findOne(options)
1485 } 1479 }
1486 1480
1481 static loadImmutableAttributes (id: number | string, t?: Transaction): Bluebird<MVideoImmutable> {
1482 const fun = () => {
1483 const query = {
1484 where: buildWhereIdOrUUID(id),
1485 transaction: t
1486 }
1487
1488 return VideoModel.scope(ScopeNames.WITH_IMMUTABLE_ATTRIBUTES).findOne(query)
1489 }
1490
1491 return ModelCache.Instance.doCache({
1492 cacheType: 'load-video-immutable-id',
1493 key: '' + id,
1494 deleteKey: 'video',
1495 fun
1496 })
1497 }
1498
1487 static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> { 1499 static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> {
1488 const where = buildWhereIdOrUUID(id) 1500 const where = buildWhereIdOrUUID(id)
1489 const options = { 1501 const options = {
@@ -1547,6 +1559,26 @@ export class VideoModel extends Model<VideoModel> {
1547 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) 1559 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query)
1548 } 1560 }
1549 1561
1562 static loadByUrlImmutableAttributes (url: string, transaction?: Transaction): Bluebird<MVideoImmutable> {
1563 const fun = () => {
1564 const query: FindOptions = {
1565 where: {
1566 url
1567 },
1568 transaction
1569 }
1570
1571 return VideoModel.scope(ScopeNames.WITH_IMMUTABLE_ATTRIBUTES).findOne(query)
1572 }
1573
1574 return ModelCache.Instance.doCache({
1575 cacheType: 'load-video-immutable-url',
1576 key: url,
1577 deleteKey: 'video',
1578 fun
1579 })
1580 }
1581
1550 static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> { 1582 static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> {
1551 const query: FindOptions = { 1583 const query: FindOptions = {
1552 where: { 1584 where: {
@@ -1593,8 +1625,8 @@ export class VideoModel extends Model<VideoModel> {
1593 } 1625 }
1594 1626
1595 static loadForGetAPI (parameters: { 1627 static loadForGetAPI (parameters: {
1596 id: number | string, 1628 id: number | string
1597 t?: Transaction, 1629 t?: Transaction
1598 userId?: number 1630 userId?: number
1599 }): Bluebird<MVideoDetails> { 1631 }): Bluebird<MVideoDetails> {
1600 const { id, t, userId } = parameters 1632 const { id, t, userId } = parameters
@@ -1660,9 +1692,9 @@ export class VideoModel extends Model<VideoModel> {
1660 static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) { 1692 static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) {
1661 // Instances only share videos 1693 // Instances only share videos
1662 const query = 'SELECT 1 FROM "videoShare" ' + 1694 const query = 'SELECT 1 FROM "videoShare" ' +
1663 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + 1695 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' +
1664 'WHERE "actorFollow"."actorId" = $followerActorId AND "videoShare"."videoId" = $videoId ' + 1696 'WHERE "actorFollow"."actorId" = $followerActorId AND "videoShare"."videoId" = $videoId ' +
1665 'LIMIT 1' 1697 'LIMIT 1'
1666 1698
1667 const options = { 1699 const options = {
1668 type: QueryTypes.SELECT as QueryTypes.SELECT, 1700 type: QueryTypes.SELECT as QueryTypes.SELECT,
@@ -1694,7 +1726,7 @@ export class VideoModel extends Model<VideoModel> {
1694 } 1726 }
1695 1727
1696 return VideoModel.findAll(query) 1728 return VideoModel.findAll(query)
1697 .then(videos => videos.map(v => v.id)) 1729 .then(videos => videos.map(v => v.id))
1698 } 1730 }
1699 1731
1700 // threshold corresponds to how many video the field should have to be returned 1732 // threshold corresponds to how many video the field should have to be returned
@@ -1714,14 +1746,14 @@ export class VideoModel extends Model<VideoModel> {
1714 limit: count, 1746 limit: count,
1715 group: field, 1747 group: field,
1716 having: Sequelize.where( 1748 having: Sequelize.where(
1717 Sequelize.fn('COUNT', Sequelize.col(field)), { [ Op.gte ]: threshold } 1749 Sequelize.fn('COUNT', Sequelize.col(field)), { [Op.gte]: threshold }
1718 ), 1750 ),
1719 order: [ (this.sequelize as any).random() ] 1751 order: [ (this.sequelize as any).random() ]
1720 } 1752 }
1721 1753
1722 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] }) 1754 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] })
1723 .findAll(query) 1755 .findAll(query)
1724 .then(rows => rows.map(r => r[ field ])) 1756 .then(rows => rows.map(r => r[field]))
1725 } 1757 }
1726 1758
1727 static buildTrendingQuery (trendingDays: number) { 1759 static buildTrendingQuery (trendingDays: number) {
@@ -1732,7 +1764,7 @@ export class VideoModel extends Model<VideoModel> {
1732 required: false, 1764 required: false,
1733 where: { 1765 where: {
1734 startDate: { 1766 startDate: {
1735 [ Op.gte ]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays) 1767 [Op.gte]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays)
1736 } 1768 }
1737 } 1769 }
1738 } 1770 }
@@ -1815,23 +1847,23 @@ export class VideoModel extends Model<VideoModel> {
1815 } 1847 }
1816 1848
1817 static getCategoryLabel (id: number) { 1849 static getCategoryLabel (id: number) {
1818 return VIDEO_CATEGORIES[ id ] || 'Misc' 1850 return VIDEO_CATEGORIES[id] || 'Misc'
1819 } 1851 }
1820 1852
1821 static getLicenceLabel (id: number) { 1853 static getLicenceLabel (id: number) {
1822 return VIDEO_LICENCES[ id ] || 'Unknown' 1854 return VIDEO_LICENCES[id] || 'Unknown'
1823 } 1855 }
1824 1856
1825 static getLanguageLabel (id: string) { 1857 static getLanguageLabel (id: string) {
1826 return VIDEO_LANGUAGES[ id ] || 'Unknown' 1858 return VIDEO_LANGUAGES[id] || 'Unknown'
1827 } 1859 }
1828 1860
1829 static getPrivacyLabel (id: number) { 1861 static getPrivacyLabel (id: number) {
1830 return VIDEO_PRIVACIES[ id ] || 'Unknown' 1862 return VIDEO_PRIVACIES[id] || 'Unknown'
1831 } 1863 }
1832 1864
1833 static getStateLabel (id: number) { 1865 static getStateLabel (id: number) {
1834 return VIDEO_STATES[ id ] || 'Unknown' 1866 return VIDEO_STATES[id] || 'Unknown'
1835 } 1867 }
1836 1868
1837 isBlacklisted () { 1869 isBlacklisted () {
@@ -1843,7 +1875,7 @@ export class VideoModel extends Model<VideoModel> {
1843 this.VideoChannel.Account.isBlocked() 1875 this.VideoChannel.Account.isBlocked()
1844 } 1876 }
1845 1877
1846 getQualityFileBy <T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) { 1878 getQualityFileBy<T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) {
1847 if (Array.isArray(this.VideoFiles) && this.VideoFiles.length !== 0) { 1879 if (Array.isArray(this.VideoFiles) && this.VideoFiles.length !== 0) {
1848 const file = fun(this.VideoFiles, file => file.resolution) 1880 const file = fun(this.VideoFiles, file => file.resolution)
1849 1881
@@ -1861,15 +1893,15 @@ export class VideoModel extends Model<VideoModel> {
1861 return undefined 1893 return undefined
1862 } 1894 }
1863 1895
1864 getMaxQualityFile <T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { 1896 getMaxQualityFile<T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo {
1865 return this.getQualityFileBy(maxBy) 1897 return this.getQualityFileBy(maxBy)
1866 } 1898 }
1867 1899
1868 getMinQualityFile <T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { 1900 getMinQualityFile<T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo {
1869 return this.getQualityFileBy(minBy) 1901 return this.getQualityFileBy(minBy)
1870 } 1902 }
1871 1903
1872 getWebTorrentFile <T extends MVideoWithFile> (this: T, resolution: number): MVideoFileVideo { 1904 getWebTorrentFile<T extends MVideoWithFile> (this: T, resolution: number): MVideoFileVideo {
1873 if (Array.isArray(this.VideoFiles) === false) return undefined 1905 if (Array.isArray(this.VideoFiles) === false) return undefined
1874 1906
1875 const file = this.VideoFiles.find(f => f.resolution === resolution) 1907 const file = this.VideoFiles.find(f => f.resolution === resolution)
@@ -1992,8 +2024,8 @@ export class VideoModel extends Model<VideoModel> {
1992 } 2024 }
1993 2025
1994 this.VideoStreamingPlaylists = this.VideoStreamingPlaylists 2026 this.VideoStreamingPlaylists = this.VideoStreamingPlaylists
1995 .filter(s => s.type !== VideoStreamingPlaylistType.HLS) 2027 .filter(s => s.type !== VideoStreamingPlaylistType.HLS)
1996 .concat(toAdd) 2028 .concat(toAdd)
1997 } 2029 }
1998 2030
1999 removeFile (videoFile: MVideoFile, isRedundancy = false) { 2031 removeFile (videoFile: MVideoFile, isRedundancy = false) {
@@ -2014,7 +2046,7 @@ export class VideoModel extends Model<VideoModel> {
2014 await remove(directoryPath) 2046 await remove(directoryPath)
2015 2047
2016 if (isRedundancy !== true) { 2048 if (isRedundancy !== true) {
2017 let streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo 2049 const streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo
2018 streamingPlaylistWithFiles.Video = this 2050 streamingPlaylistWithFiles.Video = this
2019 2051
2020 if (!Array.isArray(streamingPlaylistWithFiles.VideoFiles)) { 2052 if (!Array.isArray(streamingPlaylistWithFiles.VideoFiles)) {
diff --git a/server/tests/api/activitypub/client.ts b/server/tests/api/activitypub/client.ts
index 34c6be49b..d16f05108 100644
--- a/server/tests/api/activitypub/client.ts
+++ b/server/tests/api/activitypub/client.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -6,8 +6,6 @@ import {
6 cleanupTests, 6 cleanupTests,
7 doubleFollow, 7 doubleFollow,
8 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
9 flushTests,
10 killallServers,
11 makeActivityPubGetRequest, 9 makeActivityPubGetRequest,
12 ServerInfo, 10 ServerInfo,
13 setAccessTokensToServers, 11 setAccessTokensToServers,
diff --git a/server/tests/api/activitypub/fetch.ts b/server/tests/api/activitypub/fetch.ts
index 3d54c2042..35fd94eed 100644
--- a/server/tests/api/activitypub/fetch.ts
+++ b/server/tests/api/activitypub/fetch.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
@@ -8,9 +8,7 @@ import {
8 createUser, 8 createUser,
9 doubleFollow, 9 doubleFollow,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
11 flushTests,
12 getVideosListSort, 11 getVideosListSort,
13 killallServers,
14 ServerInfo, 12 ServerInfo,
15 setAccessTokensToServers, 13 setAccessTokensToServers,
16 setActorField, 14 setActorField,
diff --git a/server/tests/api/activitypub/helpers.ts b/server/tests/api/activitypub/helpers.ts
index 8c00ba3d6..60d95b823 100644
--- a/server/tests/api/activitypub/helpers.ts
+++ b/server/tests/api/activitypub/helpers.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
diff --git a/server/tests/api/activitypub/refresher.ts b/server/tests/api/activitypub/refresher.ts
index aa4bc6c0f..232c5d823 100644
--- a/server/tests/api/activitypub/refresher.ts
+++ b/server/tests/api/activitypub/refresher.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import {
@@ -43,32 +43,32 @@ describe('Test AP refresher', function () {
43 await setDefaultVideoChannel(servers) 43 await setDefaultVideoChannel(servers)
44 44
45 { 45 {
46 videoUUID1 = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video1' })).uuid 46 videoUUID1 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video1' })).uuid
47 videoUUID2 = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video2' })).uuid 47 videoUUID2 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video2' })).uuid
48 videoUUID3 = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video3' })).uuid 48 videoUUID3 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video3' })).uuid
49 } 49 }
50 50
51 { 51 {
52 const a1 = await generateUserAccessToken(servers[ 1 ], 'user1') 52 const a1 = await generateUserAccessToken(servers[1], 'user1')
53 await uploadVideo(servers[ 1 ].url, a1, { name: 'video4' }) 53 await uploadVideo(servers[1].url, a1, { name: 'video4' })
54 54
55 const a2 = await generateUserAccessToken(servers[ 1 ], 'user2') 55 const a2 = await generateUserAccessToken(servers[1], 'user2')
56 await uploadVideo(servers[ 1 ].url, a2, { name: 'video5' }) 56 await uploadVideo(servers[1].url, a2, { name: 'video5' })
57 } 57 }
58 58
59 { 59 {
60 const playlistAttrs = { displayName: 'playlist1', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[ 1 ].videoChannel.id } 60 const playlistAttrs = { displayName: 'playlist1', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].videoChannel.id }
61 const res = await createVideoPlaylist({ url: servers[ 1 ].url, token: servers[ 1 ].accessToken, playlistAttrs }) 61 const res = await createVideoPlaylist({ url: servers[1].url, token: servers[1].accessToken, playlistAttrs })
62 playlistUUID1 = res.body.videoPlaylist.uuid 62 playlistUUID1 = res.body.videoPlaylist.uuid
63 } 63 }
64 64
65 { 65 {
66 const playlistAttrs = { displayName: 'playlist2', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[ 1 ].videoChannel.id } 66 const playlistAttrs = { displayName: 'playlist2', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].videoChannel.id }
67 const res = await createVideoPlaylist({ url: servers[ 1 ].url, token: servers[ 1 ].accessToken, playlistAttrs }) 67 const res = await createVideoPlaylist({ url: servers[1].url, token: servers[1].accessToken, playlistAttrs })
68 playlistUUID2 = res.body.videoPlaylist.uuid 68 playlistUUID2 = res.body.videoPlaylist.uuid
69 } 69 }
70 70
71 await doubleFollow(servers[ 0 ], servers[ 1 ]) 71 await doubleFollow(servers[0], servers[1])
72 }) 72 })
73 73
74 describe('Videos refresher', function () { 74 describe('Videos refresher', function () {
@@ -79,34 +79,34 @@ describe('Test AP refresher', function () {
79 await wait(10000) 79 await wait(10000)
80 80
81 // Change UUID so the remote server returns a 404 81 // Change UUID so the remote server returns a 404
82 await setVideoField(servers[ 1 ].internalServerNumber, videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f') 82 await setVideoField(servers[1].internalServerNumber, videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f')
83 83
84 await getVideo(servers[ 0 ].url, videoUUID1) 84 await getVideo(servers[0].url, videoUUID1)
85 await getVideo(servers[ 0 ].url, videoUUID2) 85 await getVideo(servers[0].url, videoUUID2)
86 86
87 await waitJobs(servers) 87 await waitJobs(servers)
88 88
89 await getVideo(servers[ 0 ].url, videoUUID1, 404) 89 await getVideo(servers[0].url, videoUUID1, 404)
90 await getVideo(servers[ 0 ].url, videoUUID2, 200) 90 await getVideo(servers[0].url, videoUUID2, 200)
91 }) 91 })
92 92
93 it('Should not update a remote video if the remote instance is down', async function () { 93 it('Should not update a remote video if the remote instance is down', async function () {
94 this.timeout(70000) 94 this.timeout(70000)
95 95
96 killallServers([ servers[ 1 ] ]) 96 killallServers([ servers[1] ])
97 97
98 await setVideoField(servers[ 1 ].internalServerNumber, videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e') 98 await setVideoField(servers[1].internalServerNumber, videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e')
99 99
100 // Video will need a refresh 100 // Video will need a refresh
101 await wait(10000) 101 await wait(10000)
102 102
103 await getVideo(servers[ 0 ].url, videoUUID3) 103 await getVideo(servers[0].url, videoUUID3)
104 // The refresh should fail 104 // The refresh should fail
105 await waitJobs([ servers[ 0 ] ]) 105 await waitJobs([ servers[0] ])
106 106
107 await reRunServer(servers[ 1 ]) 107 await reRunServer(servers[1])
108 108
109 await getVideo(servers[ 0 ].url, videoUUID3, 200) 109 await getVideo(servers[0].url, videoUUID3, 200)
110 }) 110 })
111 }) 111 })
112 112
@@ -118,16 +118,16 @@ describe('Test AP refresher', function () {
118 await wait(10000) 118 await wait(10000)
119 119
120 // Change actor name so the remote server returns a 404 120 // Change actor name so the remote server returns a 404
121 const to = 'http://localhost:' + servers[ 1 ].port + '/accounts/user2' 121 const to = 'http://localhost:' + servers[1].port + '/accounts/user2'
122 await setActorField(servers[ 1 ].internalServerNumber, to, 'preferredUsername', 'toto') 122 await setActorField(servers[1].internalServerNumber, to, 'preferredUsername', 'toto')
123 123
124 await getAccount(servers[ 0 ].url, 'user1@localhost:' + servers[ 1 ].port) 124 await getAccount(servers[0].url, 'user1@localhost:' + servers[1].port)
125 await getAccount(servers[ 0 ].url, 'user2@localhost:' + servers[ 1 ].port) 125 await getAccount(servers[0].url, 'user2@localhost:' + servers[1].port)
126 126
127 await waitJobs(servers) 127 await waitJobs(servers)
128 128
129 await getAccount(servers[ 0 ].url, 'user1@localhost:' + servers[ 1 ].port, 200) 129 await getAccount(servers[0].url, 'user1@localhost:' + servers[1].port, 200)
130 await getAccount(servers[ 0 ].url, 'user2@localhost:' + servers[ 1 ].port, 404) 130 await getAccount(servers[0].url, 'user2@localhost:' + servers[1].port, 404)
131 }) 131 })
132 }) 132 })
133 133
@@ -139,15 +139,15 @@ describe('Test AP refresher', function () {
139 await wait(10000) 139 await wait(10000)
140 140
141 // Change UUID so the remote server returns a 404 141 // Change UUID so the remote server returns a 404
142 await setPlaylistField(servers[ 1 ].internalServerNumber, playlistUUID2, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b178e') 142 await setPlaylistField(servers[1].internalServerNumber, playlistUUID2, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b178e')
143 143
144 await getVideoPlaylist(servers[ 0 ].url, playlistUUID1) 144 await getVideoPlaylist(servers[0].url, playlistUUID1)
145 await getVideoPlaylist(servers[ 0 ].url, playlistUUID2) 145 await getVideoPlaylist(servers[0].url, playlistUUID2)
146 146
147 await waitJobs(servers) 147 await waitJobs(servers)
148 148
149 await getVideoPlaylist(servers[ 0 ].url, playlistUUID1, 200) 149 await getVideoPlaylist(servers[0].url, playlistUUID1, 200)
150 await getVideoPlaylist(servers[ 0 ].url, playlistUUID2, 404) 150 await getVideoPlaylist(servers[0].url, playlistUUID2, 404)
151 }) 151 })
152 }) 152 })
153 153
diff --git a/server/tests/api/activitypub/security.ts b/server/tests/api/activitypub/security.ts
index dc960c5c3..7e58bf065 100644
--- a/server/tests/api/activitypub/security.ts
+++ b/server/tests/api/activitypub/security.ts
@@ -1,15 +1,8 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
5import { 5import { cleanupTests, closeAllSequelize, flushAndRunMultipleServers, ServerInfo, setActorField } from '../../../../shared/extra-utils'
6 cleanupTests,
7 closeAllSequelize,
8 flushAndRunMultipleServers,
9 killallServers,
10 ServerInfo,
11 setActorField
12} from '../../../../shared/extra-utils'
13import { HTTP_SIGNATURE } from '../../../initializers/constants' 6import { HTTP_SIGNATURE } from '../../../initializers/constants'
14import { buildDigest, buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils' 7import { buildDigest, buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils'
15import * as chai from 'chai' 8import * as chai from 'chai'
@@ -33,7 +26,7 @@ function getAnnounceWithoutContext (server2: ServerInfo) {
33 if (Array.isArray(json[key])) { 26 if (Array.isArray(json[key])) {
34 result[key] = json[key].map(v => v.replace(':9002', `:${server2.port}`)) 27 result[key] = json[key].map(v => v.replace(':9002', `:${server2.port}`))
35 } else { 28 } else {
36 result[ key ] = json[ key ].replace(':9002', `:${server2.port}`) 29 result[key] = json[key].replace(':9002', `:${server2.port}`)
37 } 30 }
38 } 31 }
39 32
diff --git a/server/tests/api/check-params/accounts.ts b/server/tests/api/check-params/accounts.ts
index 4f79685bd..c29af7cd7 100644
--- a/server/tests/api/check-params/accounts.ts
+++ b/server/tests/api/check-params/accounts.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/blocklist.ts b/server/tests/api/check-params/blocklist.ts
index 0661676ce..fb459f756 100644
--- a/server/tests/api/check-params/blocklist.ts
+++ b/server/tests/api/check-params/blocklist.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts
index 443fbcb60..f1a79806b 100644
--- a/server/tests/api/check-params/config.ts
+++ b/server/tests/api/check-params/config.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { omit } from 'lodash' 3import { omit } from 'lodash'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/check-params/contact-form.ts b/server/tests/api/check-params/contact-form.ts
index b3051945e..b2126b9b0 100644
--- a/server/tests/api/check-params/contact-form.ts
+++ b/server/tests/api/check-params/contact-form.ts
@@ -1,22 +1,8 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
5import { 5import { cleanupTests, flushAndRunServer, immutableAssign, killallServers, reRunServer, ServerInfo } from '../../../../shared/extra-utils'
6 flushTests,
7 immutableAssign,
8 killallServers,
9 reRunServer,
10 flushAndRunServer,
11 ServerInfo,
12 setAccessTokensToServers, cleanupTests
13} from '../../../../shared/extra-utils'
14import {
15 checkBadCountPagination,
16 checkBadSortPagination,
17 checkBadStartPagination
18} from '../../../../shared/extra-utils/requests/check-api-params'
19import { getAccount } from '../../../../shared/extra-utils/users/accounts'
20import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form' 6import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form'
21import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' 7import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
22 8
diff --git a/server/tests/api/check-params/debug.ts b/server/tests/api/check-params/debug.ts
index 8dad26723..5fac73485 100644
--- a/server/tests/api/check-params/debug.ts
+++ b/server/tests/api/check-params/debug.ts
@@ -1,15 +1,14 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
5import { 5import {
6 cleanupTests,
6 createUser, 7 createUser,
7 flushTests,
8 killallServers,
9 flushAndRunServer, 8 flushAndRunServer,
10 ServerInfo, 9 ServerInfo,
11 setAccessTokensToServers, 10 setAccessTokensToServers,
12 userLogin, cleanupTests 11 userLogin
13} from '../../../../shared/extra-utils' 12} from '../../../../shared/extra-utils'
14import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests' 13import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests'
15 14
diff --git a/server/tests/api/check-params/follows.ts b/server/tests/api/check-params/follows.ts
index be2a603a3..2c2224a45 100644
--- a/server/tests/api/check-params/follows.ts
+++ b/server/tests/api/check-params/follows.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/jobs.ts b/server/tests/api/check-params/jobs.ts
index 22e237964..8f4af8d16 100644
--- a/server/tests/api/check-params/jobs.ts
+++ b/server/tests/api/check-params/jobs.ts
@@ -1,16 +1,14 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
5import { 5import {
6 cleanupTests,
6 createUser, 7 createUser,
7 flushTests,
8 killallServers,
9 flushAndRunServer, 8 flushAndRunServer,
10 ServerInfo, 9 ServerInfo,
11 setAccessTokensToServers, 10 setAccessTokensToServers,
12 userLogin, 11 userLogin
13 cleanupTests
14} from '../../../../shared/extra-utils' 12} from '../../../../shared/extra-utils'
15import { 13import {
16 checkBadCountPagination, 14 checkBadCountPagination,
diff --git a/server/tests/api/check-params/logs.ts b/server/tests/api/check-params/logs.ts
index f9d96bcc0..719da54e6 100644
--- a/server/tests/api/check-params/logs.ts
+++ b/server/tests/api/check-params/logs.ts
@@ -1,16 +1,14 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
5import { 5import {
6 cleanupTests,
6 createUser, 7 createUser,
7 flushTests,
8 killallServers,
9 flushAndRunServer, 8 flushAndRunServer,
10 ServerInfo, 9 ServerInfo,
11 setAccessTokensToServers, 10 setAccessTokensToServers,
12 userLogin, 11 userLogin
13 cleanupTests
14} from '../../../../shared/extra-utils' 12} from '../../../../shared/extra-utils'
15import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests' 13import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests'
16 14
diff --git a/server/tests/api/check-params/plugins.ts b/server/tests/api/check-params/plugins.ts
index 9553bce17..cf80b35c2 100644
--- a/server/tests/api/check-params/plugins.ts
+++ b/server/tests/api/check-params/plugins.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/redundancy.ts b/server/tests/api/check-params/redundancy.ts
index 6471da840..b2370a094 100644
--- a/server/tests/api/check-params/redundancy.ts
+++ b/server/tests/api/check-params/redundancy.ts
@@ -1,23 +1,27 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
5import { 5import {
6 checkBadCountPagination,
7 checkBadSortPagination,
8 checkBadStartPagination,
6 cleanupTests, 9 cleanupTests,
7 createUser, 10 createUser,
8 doubleFollow, 11 doubleFollow,
9 flushAndRunMultipleServers, 12 flushAndRunMultipleServers, makeDeleteRequest,
10 flushTests, 13 makeGetRequest, makePostBodyRequest,
11 killallServers,
12 makePutBodyRequest, 14 makePutBodyRequest,
13 ServerInfo, 15 ServerInfo,
14 setAccessTokensToServers, 16 setAccessTokensToServers, uploadVideoAndGetId,
15 userLogin 17 userLogin, waitJobs
16} from '../../../../shared/extra-utils' 18} from '../../../../shared/extra-utils'
17 19
18describe('Test server redundancy API validators', function () { 20describe('Test server redundancy API validators', function () {
19 let servers: ServerInfo[] 21 let servers: ServerInfo[]
20 let userAccessToken = null 22 let userAccessToken = null
23 let videoIdLocal: number
24 let videoIdRemote: number
21 25
22 // --------------------------------------------------------------- 26 // ---------------------------------------------------------------
23 27
@@ -34,11 +38,136 @@ describe('Test server redundancy API validators', function () {
34 password: 'password' 38 password: 'password'
35 } 39 }
36 40
37 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) 41 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
38 userAccessToken = await userLogin(servers[0], user) 42 userAccessToken = await userLogin(servers[0], user)
43
44 videoIdLocal = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video' })).id
45 videoIdRemote = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video' })).id
46
47 await waitJobs(servers)
48 })
49
50 describe('When listing redundancies', function () {
51 const path = '/api/v1/server/redundancy/videos'
52
53 let url: string
54 let token: string
55
56 before(function () {
57 url = servers[0].url
58 token = servers[0].accessToken
59 })
60
61 it('Should fail with an invalid token', async function () {
62 await makeGetRequest({ url, path, token: 'fake_token', statusCodeExpected: 401 })
63 })
64
65 it('Should fail if the user is not an administrator', async function () {
66 await makeGetRequest({ url, path, token: userAccessToken, statusCodeExpected: 403 })
67 })
68
69 it('Should fail with a bad start pagination', async function () {
70 await checkBadStartPagination(url, path, servers[0].accessToken)
71 })
72
73 it('Should fail with a bad count pagination', async function () {
74 await checkBadCountPagination(url, path, servers[0].accessToken)
75 })
76
77 it('Should fail with an incorrect sort', async function () {
78 await checkBadSortPagination(url, path, servers[0].accessToken)
79 })
80
81 it('Should fail with a bad target', async function () {
82 await makeGetRequest({ url, path, token, query: { target: 'bad target' } })
83 })
84
85 it('Should fail without target', async function () {
86 await makeGetRequest({ url, path, token })
87 })
88
89 it('Should succeed with the correct params', async function () {
90 await makeGetRequest({ url, path, token, query: { target: 'my-videos' }, statusCodeExpected: 200 })
91 })
92 })
93
94 describe('When manually adding a redundancy', function () {
95 const path = '/api/v1/server/redundancy/videos'
96
97 let url: string
98 let token: string
99
100 before(function () {
101 url = servers[0].url
102 token = servers[0].accessToken
103 })
104
105 it('Should fail with an invalid token', async function () {
106 await makePostBodyRequest({ url, path, token: 'fake_token', statusCodeExpected: 401 })
107 })
108
109 it('Should fail if the user is not an administrator', async function () {
110 await makePostBodyRequest({ url, path, token: userAccessToken, statusCodeExpected: 403 })
111 })
112
113 it('Should fail without a video id', async function () {
114 await makePostBodyRequest({ url, path, token })
115 })
116
117 it('Should fail with an incorrect video id', async function () {
118 await makePostBodyRequest({ url, path, token, fields: { videoId: 'peertube' } })
119 })
120
121 it('Should fail with a not found video id', async function () {
122 await makePostBodyRequest({ url, path, token, fields: { videoId: 6565 }, statusCodeExpected: 404 })
123 })
124
125 it('Should fail with a local a video id', async function () {
126 await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdLocal } })
127 })
128
129 it('Should succeed with the correct params', async function () {
130 await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdRemote }, statusCodeExpected: 204 })
131 })
132
133 it('Should fail if the video is already duplicated', async function () {
134 this.timeout(30000)
135
136 await waitJobs(servers)
137
138 await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdRemote }, statusCodeExpected: 409 })
139 })
140 })
141
142 describe('When manually removing a redundancy', function () {
143 const path = '/api/v1/server/redundancy/videos/'
144
145 let url: string
146 let token: string
147
148 before(function () {
149 url = servers[0].url
150 token = servers[0].accessToken
151 })
152
153 it('Should fail with an invalid token', async function () {
154 await makeDeleteRequest({ url, path: path + '1', token: 'fake_token', statusCodeExpected: 401 })
155 })
156
157 it('Should fail if the user is not an administrator', async function () {
158 await makeDeleteRequest({ url, path: path + '1', token: userAccessToken, statusCodeExpected: 403 })
159 })
160
161 it('Should fail with an incorrect video id', async function () {
162 await makeDeleteRequest({ url, path: path + 'toto', token })
163 })
164
165 it('Should fail with a not found video redundancy', async function () {
166 await makeDeleteRequest({ url, path: path + '454545', token, statusCodeExpected: 404 })
167 })
39 }) 168 })
40 169
41 describe('When updating redundancy', function () { 170 describe('When updating server redundancy', function () {
42 const path = '/api/v1/server/redundancy' 171 const path = '/api/v1/server/redundancy'
43 172
44 it('Should fail with an invalid token', async function () { 173 it('Should fail with an invalid token', async function () {
diff --git a/server/tests/api/check-params/search.ts b/server/tests/api/check-params/search.ts
index 8ad9d98bf..f8d0cd4ec 100644
--- a/server/tests/api/check-params/search.ts
+++ b/server/tests/api/check-params/search.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/services.ts b/server/tests/api/check-params/services.ts
index d15753aed..457adfaab 100644
--- a/server/tests/api/check-params/services.ts
+++ b/server/tests/api/check-params/services.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/user-notifications.ts b/server/tests/api/check-params/user-notifications.ts
index 3b06be7ef..2048fa667 100644
--- a/server/tests/api/check-params/user-notifications.ts
+++ b/server/tests/api/check-params/user-notifications.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as io from 'socket.io-client' 4import * as io from 'socket.io-client'
diff --git a/server/tests/api/check-params/user-subscriptions.ts b/server/tests/api/check-params/user-subscriptions.ts
index fa36c4078..1edba4d64 100644
--- a/server/tests/api/check-params/user-subscriptions.ts
+++ b/server/tests/api/check-params/user-subscriptions.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index 5d5af284c..f448bb2a6 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { omit } from 'lodash' 3import { omit } from 'lodash'
4import 'mocha' 4import 'mocha'
@@ -50,6 +50,7 @@ describe('Test users API validators', function () {
50 let serverWithRegistrationDisabled: ServerInfo 50 let serverWithRegistrationDisabled: ServerInfo
51 let userAccessToken = '' 51 let userAccessToken = ''
52 let moderatorAccessToken = '' 52 let moderatorAccessToken = ''
53 // eslint-disable-next-line @typescript-eslint/no-unused-vars
53 let channelId: number 54 let channelId: number
54 55
55 // --------------------------------------------------------------- 56 // ---------------------------------------------------------------
@@ -120,7 +121,7 @@ describe('Test users API validators', function () {
120 121
121 { 122 {
122 const res = await getMyUserInformation(server.url, server.accessToken) 123 const res = await getMyUserInformation(server.url, server.accessToken)
123 channelId = res.body.videoChannels[ 0 ].id 124 channelId = res.body.videoChannels[0].id
124 } 125 }
125 126
126 { 127 {
@@ -529,7 +530,7 @@ describe('Test users API validators', function () {
529 it('Should fail without an incorrect input file', async function () { 530 it('Should fail without an incorrect input file', async function () {
530 const fields = {} 531 const fields = {}
531 const attaches = { 532 const attaches = {
532 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') 533 avatarfile: join(__dirname, '..', '..', 'fixtures', 'video_short.mp4')
533 } 534 }
534 await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) 535 await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
535 }) 536 })
@@ -537,7 +538,7 @@ describe('Test users API validators', function () {
537 it('Should fail with a big file', async function () { 538 it('Should fail with a big file', async function () {
538 const fields = {} 539 const fields = {}
539 const attaches = { 540 const attaches = {
540 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') 541 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png')
541 } 542 }
542 await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) 543 await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
543 }) 544 })
@@ -545,7 +546,7 @@ describe('Test users API validators', function () {
545 it('Should fail with an unauthenticated user', async function () { 546 it('Should fail with an unauthenticated user', async function () {
546 const fields = {} 547 const fields = {}
547 const attaches = { 548 const attaches = {
548 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') 549 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
549 } 550 }
550 await makeUploadRequest({ 551 await makeUploadRequest({
551 url: server.url, 552 url: server.url,
@@ -559,7 +560,7 @@ describe('Test users API validators', function () {
559 it('Should succeed with the correct params', async function () { 560 it('Should succeed with the correct params', async function () {
560 const fields = {} 561 const fields = {}
561 const attaches = { 562 const attaches = {
562 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') 563 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
563 } 564 }
564 await makeUploadRequest({ 565 await makeUploadRequest({
565 url: server.url, 566 url: server.url,
diff --git a/server/tests/api/check-params/video-abuses.ts b/server/tests/api/check-params/video-abuses.ts
index bf29f8d4d..bea2177f3 100644
--- a/server/tests/api/check-params/video-abuses.ts
+++ b/server/tests/api/check-params/video-abuses.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
@@ -126,6 +126,7 @@ describe('Test video abuses API validators', function () {
126 126
127 describe('When updating a video abuse', function () { 127 describe('When updating a video abuse', function () {
128 const basePath = '/api/v1/videos/' 128 const basePath = '/api/v1/videos/'
129 // eslint-disable-next-line @typescript-eslint/no-unused-vars
129 let path: string 130 let path: string
130 131
131 before(() => { 132 before(() => {
@@ -163,6 +164,7 @@ describe('Test video abuses API validators', function () {
163 164
164 describe('When deleting a video abuse', function () { 165 describe('When deleting a video abuse', function () {
165 const basePath = '/api/v1/videos/' 166 const basePath = '/api/v1/videos/'
167 // eslint-disable-next-line @typescript-eslint/no-unused-vars
166 let path: string 168 let path: string
167 169
168 before(() => { 170 before(() => {
diff --git a/server/tests/api/check-params/video-blacklist.ts b/server/tests/api/check-params/video-blacklist.ts
index 6466888fb..145f43980 100644
--- a/server/tests/api/check-params/video-blacklist.ts
+++ b/server/tests/api/check-params/video-blacklist.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
@@ -7,25 +7,24 @@ import {
7 createUser, 7 createUser,
8 doubleFollow, 8 doubleFollow,
9 flushAndRunMultipleServers, 9 flushAndRunMultipleServers,
10 flushTests,
11 getBlacklistedVideosList, 10 getBlacklistedVideosList,
12 getVideo, 11 getVideo,
13 getVideoWithToken, 12 getVideoWithToken,
14 killallServers,
15 makePostBodyRequest, 13 makePostBodyRequest,
16 makePutBodyRequest, 14 makePutBodyRequest,
17 removeVideoFromBlacklist, 15 removeVideoFromBlacklist,
18 ServerInfo, 16 ServerInfo,
19 setAccessTokensToServers, 17 setAccessTokensToServers,
20 uploadVideo, 18 uploadVideo,
21 userLogin, waitJobs 19 userLogin,
20 waitJobs
22} from '../../../../shared/extra-utils' 21} from '../../../../shared/extra-utils'
23import { 22import {
24 checkBadCountPagination, 23 checkBadCountPagination,
25 checkBadSortPagination, 24 checkBadSortPagination,
26 checkBadStartPagination 25 checkBadStartPagination
27} from '../../../../shared/extra-utils/requests/check-api-params' 26} from '../../../../shared/extra-utils/requests/check-api-params'
28import { VideoDetails, VideoBlacklistType } from '../../../../shared/models/videos' 27import { VideoBlacklistType, VideoDetails } from '../../../../shared/models/videos'
29import { expect } from 'chai' 28import { expect } from 'chai'
30 29
31describe('Test video blacklist API validators', function () { 30describe('Test video blacklist API validators', function () {
@@ -48,14 +47,14 @@ describe('Test video blacklist API validators', function () {
48 { 47 {
49 const username = 'user1' 48 const username = 'user1'
50 const password = 'my super password' 49 const password = 'my super password'
51 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: username, password: password }) 50 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: username, password: password })
52 userAccessToken1 = await userLogin(servers[0], { username, password }) 51 userAccessToken1 = await userLogin(servers[0], { username, password })
53 } 52 }
54 53
55 { 54 {
56 const username = 'user2' 55 const username = 'user2'
57 const password = 'my super password' 56 const password = 'my super password'
58 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: username, password: password }) 57 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: username, password: password })
59 userAccessToken2 = await userLogin(servers[0], { username, password }) 58 userAccessToken2 = await userLogin(servers[0], { username, password })
60 } 59 }
61 60
@@ -120,7 +119,7 @@ describe('Test video blacklist API validators', function () {
120 119
121 it('Should succeed with the correct params', async function () { 120 it('Should succeed with the correct params', async function () {
122 const path = basePath + servers[0].video.uuid + '/blacklist' 121 const path = basePath + servers[0].video.uuid + '/blacklist'
123 const fields = { } 122 const fields = {}
124 123
125 await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields, statusCodeExpected: 204 }) 124 await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields, statusCodeExpected: 204 })
126 }) 125 })
diff --git a/server/tests/api/check-params/video-captions.ts b/server/tests/api/check-params/video-captions.ts
index 6ddc20d69..a5f5c3322 100644
--- a/server/tests/api/check-params/video-captions.ts
+++ b/server/tests/api/check-params/video-captions.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import {
@@ -50,7 +50,7 @@ describe('Test video captions API validator', function () {
50 describe('When adding video caption', function () { 50 describe('When adding video caption', function () {
51 const fields = { } 51 const fields = { }
52 const attaches = { 52 const attaches = {
53 'captionfile': join(__dirname, '..', '..', 'fixtures', 'subtitle-good1.vtt') 53 captionfile: join(__dirname, '..', '..', 'fixtures', 'subtitle-good1.vtt')
54 } 54 }
55 55
56 it('Should fail without a valid uuid', async function () { 56 it('Should fail without a valid uuid', async function () {
diff --git a/server/tests/api/check-params/video-channels.ts b/server/tests/api/check-params/video-channels.ts
index de88298d1..2795ad7d5 100644
--- a/server/tests/api/check-params/video-channels.ts
+++ b/server/tests/api/check-params/video-channels.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
@@ -243,7 +243,7 @@ describe('Test video channels API validator', function () {
243 it('Should fail with an incorrect input file', async function () { 243 it('Should fail with an incorrect input file', async function () {
244 const fields = {} 244 const fields = {}
245 const attaches = { 245 const attaches = {
246 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') 246 avatarfile: join(__dirname, '..', '..', 'fixtures', 'video_short.mp4')
247 } 247 }
248 await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches }) 248 await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches })
249 }) 249 })
@@ -251,7 +251,7 @@ describe('Test video channels API validator', function () {
251 it('Should fail with a big file', async function () { 251 it('Should fail with a big file', async function () {
252 const fields = {} 252 const fields = {}
253 const attaches = { 253 const attaches = {
254 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') 254 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png')
255 } 255 }
256 await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches }) 256 await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches })
257 }) 257 })
@@ -259,7 +259,7 @@ describe('Test video channels API validator', function () {
259 it('Should fail with an unauthenticated user', async function () { 259 it('Should fail with an unauthenticated user', async function () {
260 const fields = {} 260 const fields = {}
261 const attaches = { 261 const attaches = {
262 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') 262 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
263 } 263 }
264 await makeUploadRequest({ 264 await makeUploadRequest({
265 url: server.url, 265 url: server.url,
@@ -273,7 +273,7 @@ describe('Test video channels API validator', function () {
273 it('Should succeed with the correct params', async function () { 273 it('Should succeed with the correct params', async function () {
274 const fields = {} 274 const fields = {}
275 const attaches = { 275 const attaches = {
276 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') 276 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
277 } 277 }
278 await makeUploadRequest({ 278 await makeUploadRequest({
279 url: server.url, 279 url: server.url,
@@ -324,7 +324,7 @@ describe('Test video channels API validator', function () {
324 }) 324 })
325 325
326 it('Should fail with an unknown video channel id', async function () { 326 it('Should fail with an unknown video channel id', async function () {
327 await deleteVideoChannel(server.url, server.accessToken,'super_channel2', 404) 327 await deleteVideoChannel(server.url, server.accessToken, 'super_channel2', 404)
328 }) 328 })
329 329
330 it('Should succeed with the correct parameters', async function () { 330 it('Should succeed with the correct parameters', async function () {
diff --git a/server/tests/api/check-params/video-comments.ts b/server/tests/api/check-params/video-comments.ts
index 5cf90bacc..e67cc01fa 100644
--- a/server/tests/api/check-params/video-comments.ts
+++ b/server/tests/api/check-params/video-comments.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/check-params/video-imports.ts b/server/tests/api/check-params/video-imports.ts
index 231d5cc85..dbea39c48 100644
--- a/server/tests/api/check-params/video-imports.ts
+++ b/server/tests/api/check-params/video-imports.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { omit } from 'lodash' 3import { omit } from 'lodash'
4import 'mocha' 4import 'mocha'
@@ -29,6 +29,7 @@ describe('Test video imports API validator', function () {
29 const path = '/api/v1/videos/imports' 29 const path = '/api/v1/videos/imports'
30 let server: ServerInfo 30 let server: ServerInfo
31 let userAccessToken = '' 31 let userAccessToken = ''
32 // eslint-disable-next-line @typescript-eslint/no-unused-vars
32 let accountName: string 33 let accountName: string
33 let channelId: number 34 let channelId: number
34 35
@@ -48,7 +49,7 @@ describe('Test video imports API validator', function () {
48 49
49 { 50 {
50 const res = await getMyUserInformation(server.url, server.accessToken) 51 const res = await getMyUserInformation(server.url, server.accessToken)
51 channelId = res.body.videoChannels[ 0 ].id 52 channelId = res.body.videoChannels[0].id
52 accountName = res.body.account.name + '@' + res.body.account.host 53 accountName = res.body.account.name + '@' + res.body.account.host
53 } 54 }
54 }) 55 })
@@ -196,7 +197,7 @@ describe('Test video imports API validator', function () {
196 it('Should fail with an incorrect thumbnail file', async function () { 197 it('Should fail with an incorrect thumbnail file', async function () {
197 const fields = baseCorrectParams 198 const fields = baseCorrectParams
198 const attaches = { 199 const attaches = {
199 'thumbnailfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') 200 thumbnailfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
200 } 201 }
201 202
202 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) 203 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
@@ -205,7 +206,7 @@ describe('Test video imports API validator', function () {
205 it('Should fail with a big thumbnail file', async function () { 206 it('Should fail with a big thumbnail file', async function () {
206 const fields = baseCorrectParams 207 const fields = baseCorrectParams
207 const attaches = { 208 const attaches = {
208 'thumbnailfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') 209 thumbnailfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png')
209 } 210 }
210 211
211 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) 212 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
@@ -214,7 +215,7 @@ describe('Test video imports API validator', function () {
214 it('Should fail with an incorrect preview file', async function () { 215 it('Should fail with an incorrect preview file', async function () {
215 const fields = baseCorrectParams 216 const fields = baseCorrectParams
216 const attaches = { 217 const attaches = {
217 'previewfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') 218 previewfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
218 } 219 }
219 220
220 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) 221 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
@@ -223,7 +224,7 @@ describe('Test video imports API validator', function () {
223 it('Should fail with a big preview file', async function () { 224 it('Should fail with a big preview file', async function () {
224 const fields = baseCorrectParams 225 const fields = baseCorrectParams
225 const attaches = { 226 const attaches = {
226 'previewfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') 227 previewfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png')
227 } 228 }
228 229
229 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) 230 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
@@ -232,7 +233,7 @@ describe('Test video imports API validator', function () {
232 it('Should fail with an invalid torrent file', async function () { 233 it('Should fail with an invalid torrent file', async function () {
233 const fields = omit(baseCorrectParams, 'targetUrl') 234 const fields = omit(baseCorrectParams, 'targetUrl')
234 const attaches = { 235 const attaches = {
235 'torrentfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') 236 torrentfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png')
236 } 237 }
237 238
238 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) 239 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
@@ -303,7 +304,7 @@ describe('Test video imports API validator', function () {
303 304
304 fields = omit(fields, 'magnetUri') 305 fields = omit(fields, 'magnetUri')
305 const attaches = { 306 const attaches = {
306 'torrentfile': join(__dirname, '..', '..', 'fixtures', 'video-720p.torrent') 307 torrentfile: join(__dirname, '..', '..', 'fixtures', 'video-720p.torrent')
307 } 308 }
308 309
309 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches, statusCodeExpected: 409 }) 310 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches, statusCodeExpected: 409 })
diff --git a/server/tests/api/check-params/video-playlists.ts b/server/tests/api/check-params/video-playlists.ts
index df158f3b1..0410e737a 100644
--- a/server/tests/api/check-params/video-playlists.ts
+++ b/server/tests/api/check-params/video-playlists.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import {
@@ -36,6 +36,7 @@ describe('Test video playlists API validator', function () {
36 let privatePlaylistUUID: string 36 let privatePlaylistUUID: string
37 let watchLaterPlaylistId: number 37 let watchLaterPlaylistId: number
38 let videoId: number 38 let videoId: number
39 // eslint-disable-next-line @typescript-eslint/no-unused-vars
39 let videoId2: number 40 let videoId2: number
40 let playlistElementId: number 41 let playlistElementId: number
41 42
@@ -449,7 +450,7 @@ describe('Test video playlists API validator', function () {
449 videoId3 = (await uploadVideoAndGetId({ server, videoName: 'video 3' })).id 450 videoId3 = (await uploadVideoAndGetId({ server, videoName: 'video 3' })).id
450 videoId4 = (await uploadVideoAndGetId({ server, videoName: 'video 4' })).id 451 videoId4 = (await uploadVideoAndGetId({ server, videoName: 'video 4' })).id
451 452
452 for (let id of [ videoId3, videoId4 ]) { 453 for (const id of [ videoId3, videoId4 ]) {
453 await addVideoInPlaylist({ 454 await addVideoInPlaylist({
454 url: server.url, 455 url: server.url,
455 token: server.accessToken, 456 token: server.accessToken,
@@ -476,7 +477,7 @@ describe('Test video playlists API validator', function () {
476 } 477 }
477 478
478 { 479 {
479 const params = getBase({}, { playlistId: 42, expectedStatus: 404 }) 480 const params = getBase({}, { playlistId: 42, expectedStatus: 404 })
480 await reorderVideosPlaylist(params) 481 await reorderVideosPlaylist(params)
481 } 482 }
482 }) 483 })
diff --git a/server/tests/api/check-params/videos-filter.ts b/server/tests/api/check-params/videos-filter.ts
index 811756745..ec8654db2 100644
--- a/server/tests/api/check-params/videos-filter.ts
+++ b/server/tests/api/check-params/videos-filter.ts
@@ -1,10 +1,9 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import {
5 cleanupTests, 5 cleanupTests,
6 createUser, 6 createUser,
7 createVideoPlaylist,
8 flushAndRunServer, 7 flushAndRunServer,
9 makeGetRequest, 8 makeGetRequest,
10 ServerInfo, 9 ServerInfo,
@@ -13,7 +12,6 @@ import {
13 userLogin 12 userLogin
14} from '../../../../shared/extra-utils' 13} from '../../../../shared/extra-utils'
15import { UserRole } from '../../../../shared/models/users' 14import { UserRole } from '../../../../shared/models/users'
16import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
17 15
18async function testEndpoints (server: ServerInfo, token: string, filter: string, statusCodeExpected: number) { 16async function testEndpoints (server: ServerInfo, token: string, filter: string, statusCodeExpected: number) {
19 const paths = [ 17 const paths = [
@@ -77,7 +75,7 @@ describe('Test videos filters', function () {
77 }) 75 })
78 76
79 it('Should succeed with a good filter', async function () { 77 it('Should succeed with a good filter', async function () {
80 await testEndpoints(server, server.accessToken,'local', 200) 78 await testEndpoints(server, server.accessToken, 'local', 200)
81 }) 79 })
82 80
83 it('Should fail to list all-local with a simple user', async function () { 81 it('Should fail to list all-local with a simple user', async function () {
diff --git a/server/tests/api/check-params/videos-history.ts b/server/tests/api/check-params/videos-history.ts
index 3739e3fad..941f62654 100644
--- a/server/tests/api/check-params/videos-history.ts
+++ b/server/tests/api/check-params/videos-history.ts
@@ -1,6 +1,5 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai'
4import 'mocha' 3import 'mocha'
5import { 4import {
6 checkBadCountPagination, 5 checkBadCountPagination,
@@ -15,12 +14,10 @@ import {
15 uploadVideo 14 uploadVideo
16} from '../../../../shared/extra-utils' 15} from '../../../../shared/extra-utils'
17 16
18const expect = chai.expect
19
20describe('Test videos history API validator', function () { 17describe('Test videos history API validator', function () {
18 const myHistoryPath = '/api/v1/users/me/history/videos'
19 const myHistoryRemove = myHistoryPath + '/remove'
21 let watchingPath: string 20 let watchingPath: string
22 let myHistoryPath = '/api/v1/users/me/history/videos'
23 let myHistoryRemove = myHistoryPath + '/remove'
24 let server: ServerInfo 21 let server: ServerInfo
25 22
26 // --------------------------------------------------------------- 23 // ---------------------------------------------------------------
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts
index 16ef1c505..0d4665954 100644
--- a/server/tests/api/check-params/videos.ts
+++ b/server/tests/api/check-params/videos.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
@@ -56,8 +56,8 @@ describe('Test videos API validator', function () {
56 56
57 { 57 {
58 const res = await getMyUserInformation(server.url, server.accessToken) 58 const res = await getMyUserInformation(server.url, server.accessToken)
59 channelId = res.body.videoChannels[ 0 ].id 59 channelId = res.body.videoChannels[0].id
60 channelName = res.body.videoChannels[ 0 ].name 60 channelName = res.body.videoChannels[0].name
61 accountName = res.body.account.name + '@' + res.body.account.host 61 accountName = res.body.account.name + '@' + res.body.account.host
62 } 62 }
63 }) 63 })
@@ -182,7 +182,7 @@ describe('Test videos API validator', function () {
182 describe('When adding a video', function () { 182 describe('When adding a video', function () {
183 let baseCorrectParams 183 let baseCorrectParams
184 const baseCorrectAttaches = { 184 const baseCorrectAttaches = {
185 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.webm') 185 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.webm')
186 } 186 }
187 187
188 before(function () { 188 before(function () {
@@ -330,7 +330,7 @@ describe('Test videos API validator', function () {
330 }) 330 })
331 331
332 it('Should fail with a bad originally published at attribute', async function () { 332 it('Should fail with a bad originally published at attribute', async function () {
333 const fields = immutableAssign(baseCorrectParams, { 'originallyPublishedAt': 'toto' }) 333 const fields = immutableAssign(baseCorrectParams, { originallyPublishedAt: 'toto' })
334 const attaches = baseCorrectAttaches 334 const attaches = baseCorrectAttaches
335 335
336 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 336 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
@@ -345,12 +345,12 @@ describe('Test videos API validator', function () {
345 it('Should fail with an incorrect input file', async function () { 345 it('Should fail with an incorrect input file', async function () {
346 const fields = baseCorrectParams 346 const fields = baseCorrectParams
347 let attaches = { 347 let attaches = {
348 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short_fake.webm') 348 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short_fake.webm')
349 } 349 }
350 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 350 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
351 351
352 attaches = { 352 attaches = {
353 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mkv') 353 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mkv')
354 } 354 }
355 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 355 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
356 }) 356 })
@@ -358,8 +358,8 @@ describe('Test videos API validator', function () {
358 it('Should fail with an incorrect thumbnail file', async function () { 358 it('Should fail with an incorrect thumbnail file', async function () {
359 const fields = baseCorrectParams 359 const fields = baseCorrectParams
360 const attaches = { 360 const attaches = {
361 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png'), 361 thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'avatar.png'),
362 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') 362 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4')
363 } 363 }
364 364
365 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 365 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
@@ -368,8 +368,8 @@ describe('Test videos API validator', function () {
368 it('Should fail with a big thumbnail file', async function () { 368 it('Should fail with a big thumbnail file', async function () {
369 const fields = baseCorrectParams 369 const fields = baseCorrectParams
370 const attaches = { 370 const attaches = {
371 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'), 371 thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'),
372 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') 372 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4')
373 } 373 }
374 374
375 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 375 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
@@ -378,8 +378,8 @@ describe('Test videos API validator', function () {
378 it('Should fail with an incorrect preview file', async function () { 378 it('Should fail with an incorrect preview file', async function () {
379 const fields = baseCorrectParams 379 const fields = baseCorrectParams
380 const attaches = { 380 const attaches = {
381 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png'), 381 previewfile: join(root(), 'server', 'tests', 'fixtures', 'avatar.png'),
382 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') 382 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4')
383 } 383 }
384 384
385 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 385 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
@@ -388,8 +388,8 @@ describe('Test videos API validator', function () {
388 it('Should fail with a big preview file', async function () { 388 it('Should fail with a big preview file', async function () {
389 const fields = baseCorrectParams 389 const fields = baseCorrectParams
390 const attaches = { 390 const attaches = {
391 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'), 391 previewfile: join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'),
392 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') 392 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4')
393 } 393 }
394 394
395 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 395 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
@@ -566,7 +566,7 @@ describe('Test videos API validator', function () {
566 it('Should fail with an incorrect thumbnail file', async function () { 566 it('Should fail with an incorrect thumbnail file', async function () {
567 const fields = baseCorrectParams 567 const fields = baseCorrectParams
568 const attaches = { 568 const attaches = {
569 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png') 569 thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'avatar.png')
570 } 570 }
571 571
572 await makeUploadRequest({ 572 await makeUploadRequest({
@@ -582,7 +582,7 @@ describe('Test videos API validator', function () {
582 it('Should fail with a big thumbnail file', async function () { 582 it('Should fail with a big thumbnail file', async function () {
583 const fields = baseCorrectParams 583 const fields = baseCorrectParams
584 const attaches = { 584 const attaches = {
585 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png') 585 thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png')
586 } 586 }
587 587
588 await makeUploadRequest({ 588 await makeUploadRequest({
@@ -598,7 +598,7 @@ describe('Test videos API validator', function () {
598 it('Should fail with an incorrect preview file', async function () { 598 it('Should fail with an incorrect preview file', async function () {
599 const fields = baseCorrectParams 599 const fields = baseCorrectParams
600 const attaches = { 600 const attaches = {
601 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png') 601 previewfile: join(root(), 'server', 'tests', 'fixtures', 'avatar.png')
602 } 602 }
603 603
604 await makeUploadRequest({ 604 await makeUploadRequest({
@@ -614,7 +614,7 @@ describe('Test videos API validator', function () {
614 it('Should fail with a big preview file', async function () { 614 it('Should fail with a big preview file', async function () {
615 const fields = baseCorrectParams 615 const fields = baseCorrectParams
616 const attaches = { 616 const attaches = {
617 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png') 617 previewfile: join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png')
618 } 618 }
619 619
620 await makeUploadRequest({ 620 await makeUploadRequest({
diff --git a/server/tests/api/notifications/user-notifications.ts b/server/tests/api/notifications/user-notifications.ts
index 15a34f5aa..2a632e16f 100644
--- a/server/tests/api/notifications/user-notifications.ts
+++ b/server/tests/api/notifications/user-notifications.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -74,7 +74,7 @@ async function uploadVideoByRemoteAccount (servers: ServerInfo[], additionalPara
74 const name = 'remote video ' + uuidv4() 74 const name = 'remote video ' + uuidv4()
75 75
76 const data = Object.assign({ name }, additionalParams) 76 const data = Object.assign({ name }, additionalParams)
77 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, data) 77 const res = await uploadVideo(servers[1].url, servers[1].accessToken, data)
78 78
79 await waitJobs(servers) 79 await waitJobs(servers)
80 80
@@ -85,7 +85,7 @@ async function uploadVideoByLocalAccount (servers: ServerInfo[], additionalParam
85 const name = 'local video ' + uuidv4() 85 const name = 'local video ' + uuidv4()
86 86
87 const data = Object.assign({ name }, additionalParams) 87 const data = Object.assign({ name }, additionalParams)
88 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, data) 88 const res = await uploadVideo(servers[0].url, servers[0].accessToken, data)
89 89
90 await waitJobs(servers) 90 await waitJobs(servers)
91 91
@@ -95,9 +95,9 @@ async function uploadVideoByLocalAccount (servers: ServerInfo[], additionalParam
95describe('Test users notifications', function () { 95describe('Test users notifications', function () {
96 let servers: ServerInfo[] = [] 96 let servers: ServerInfo[] = []
97 let userAccessToken: string 97 let userAccessToken: string
98 let userNotifications: UserNotification[] = [] 98 const userNotifications: UserNotification[] = []
99 let adminNotifications: UserNotification[] = [] 99 const adminNotifications: UserNotification[] = []
100 let adminNotificationsServer2: UserNotification[] = [] 100 const adminNotificationsServer2: UserNotification[] = []
101 const emails: object[] = [] 101 const emails: object[] = []
102 let channelId: number 102 let channelId: number
103 103
@@ -142,8 +142,8 @@ describe('Test users notifications', function () {
142 password: 'super password' 142 password: 'super password'
143 } 143 }
144 await createUser({ 144 await createUser({
145 url: servers[ 0 ].url, 145 url: servers[0].url,
146 accessToken: servers[ 0 ].accessToken, 146 accessToken: servers[0].accessToken,
147 username: user.username, 147 username: user.username,
148 password: user.password, 148 password: user.password,
149 videoQuota: 10 * 1000 * 1000 149 videoQuota: 10 * 1000 * 1000
@@ -155,15 +155,15 @@ describe('Test users notifications', function () {
155 await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, allNotificationSettings) 155 await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, allNotificationSettings)
156 156
157 { 157 {
158 const socket = getUserNotificationSocket(servers[ 0 ].url, userAccessToken) 158 const socket = getUserNotificationSocket(servers[0].url, userAccessToken)
159 socket.on('new-notification', n => userNotifications.push(n)) 159 socket.on('new-notification', n => userNotifications.push(n))
160 } 160 }
161 { 161 {
162 const socket = getUserNotificationSocket(servers[ 0 ].url, servers[0].accessToken) 162 const socket = getUserNotificationSocket(servers[0].url, servers[0].accessToken)
163 socket.on('new-notification', n => adminNotifications.push(n)) 163 socket.on('new-notification', n => adminNotifications.push(n))
164 } 164 }
165 { 165 {
166 const socket = getUserNotificationSocket(servers[ 1 ].url, servers[1].accessToken) 166 const socket = getUserNotificationSocket(servers[1].url, servers[1].accessToken)
167 socket.on('new-notification', n => adminNotificationsServer2.push(n)) 167 socket.on('new-notification', n => adminNotificationsServer2.push(n))
168 } 168 }
169 169
@@ -190,7 +190,7 @@ describe('Test users notifications', function () {
190 190
191 await uploadVideoByLocalAccount(servers) 191 await uploadVideoByLocalAccount(servers)
192 192
193 const notification = await getLastNotification(servers[ 0 ].url, userAccessToken) 193 const notification = await getLastNotification(servers[0].url, userAccessToken)
194 expect(notification).to.be.undefined 194 expect(notification).to.be.undefined
195 195
196 expect(emails).to.have.lengthOf(0) 196 expect(emails).to.have.lengthOf(0)
@@ -221,7 +221,7 @@ describe('Test users notifications', function () {
221 this.timeout(20000) 221 this.timeout(20000)
222 222
223 // In 2 seconds 223 // In 2 seconds
224 let updateAt = new Date(new Date().getTime() + 2000) 224 const updateAt = new Date(new Date().getTime() + 2000)
225 225
226 const data = { 226 const data = {
227 privacy: VideoPrivacy.PRIVATE, 227 privacy: VideoPrivacy.PRIVATE,
@@ -240,7 +240,7 @@ describe('Test users notifications', function () {
240 this.timeout(50000) 240 this.timeout(50000)
241 241
242 // In 2 seconds 242 // In 2 seconds
243 let updateAt = new Date(new Date().getTime() + 2000) 243 const updateAt = new Date(new Date().getTime() + 2000)
244 244
245 const data = { 245 const data = {
246 privacy: VideoPrivacy.PRIVATE, 246 privacy: VideoPrivacy.PRIVATE,
@@ -259,7 +259,7 @@ describe('Test users notifications', function () {
259 it('Should not send a notification before the video is published', async function () { 259 it('Should not send a notification before the video is published', async function () {
260 this.timeout(20000) 260 this.timeout(20000)
261 261
262 let updateAt = new Date(new Date().getTime() + 1000000) 262 const updateAt = new Date(new Date().getTime() + 1000000)
263 263
264 const data = { 264 const data = {
265 privacy: VideoPrivacy.PRIVATE, 265 privacy: VideoPrivacy.PRIVATE,
@@ -386,7 +386,7 @@ describe('Test users notifications', function () {
386 it('Should not send a new comment notification if the account is muted', async function () { 386 it('Should not send a new comment notification if the account is muted', async function () {
387 this.timeout(10000) 387 this.timeout(10000)
388 388
389 await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root') 389 await addAccountToAccountBlocklist(servers[0].url, userAccessToken, 'root')
390 390
391 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' }) 391 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' })
392 const uuid = resVideo.body.video.uuid 392 const uuid = resVideo.body.video.uuid
@@ -397,7 +397,7 @@ describe('Test users notifications', function () {
397 await wait(500) 397 await wait(500)
398 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence') 398 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence')
399 399
400 await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root') 400 await removeAccountFromAccountBlocklist(servers[0].url, userAccessToken, 'root')
401 }) 401 })
402 402
403 it('Should send a new comment notification after a local comment on my video', async function () { 403 it('Should send a new comment notification after a local comment on my video', async function () {
@@ -456,9 +456,9 @@ describe('Test users notifications', function () {
456 await waitJobs(servers) 456 await waitJobs(servers)
457 457
458 { 458 {
459 const resThread = await addVideoCommentThread(servers[ 1 ].url, servers[ 1 ].accessToken, uuid, 'comment') 459 const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'comment')
460 const threadId = resThread.body.comment.id 460 const threadId = resThread.body.comment.id
461 await addVideoCommentReply(servers[ 1 ].url, servers[ 1 ].accessToken, uuid, threadId, 'reply') 461 await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, threadId, 'reply')
462 } 462 }
463 463
464 await waitJobs(servers) 464 await waitJobs(servers)
@@ -530,7 +530,7 @@ describe('Test users notifications', function () {
530 it('Should not send a new mention notification if the account is muted', async function () { 530 it('Should not send a new mention notification if the account is muted', async function () {
531 this.timeout(10000) 531 this.timeout(10000)
532 532
533 await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root') 533 await addAccountToAccountBlocklist(servers[0].url, userAccessToken, 'root')
534 534
535 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' }) 535 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' })
536 const uuid = resVideo.body.video.uuid 536 const uuid = resVideo.body.video.uuid
@@ -541,7 +541,7 @@ describe('Test users notifications', function () {
541 await wait(500) 541 await wait(500)
542 await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence') 542 await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence')
543 543
544 await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root') 544 await removeAccountFromAccountBlocklist(servers[0].url, userAccessToken, 'root')
545 }) 545 })
546 546
547 it('Should not send a new mention notification if the remote account mention a local account', async function () { 547 it('Should not send a new mention notification if the remote account mention a local account', async function () {
@@ -585,7 +585,7 @@ describe('Test users notifications', function () {
585 585
586 await waitJobs(servers) 586 await waitJobs(servers)
587 587
588 const text1 = `hello @user_1@localhost:${servers[ 0 ].port} 1` 588 const text1 = `hello @user_1@localhost:${servers[0].port} 1`
589 const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, text1) 589 const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, text1)
590 const server2ThreadId = resThread.body.comment.id 590 const server2ThreadId = resThread.body.comment.id
591 591
@@ -596,7 +596,7 @@ describe('Test users notifications', function () {
596 const server1ThreadId = resThread2.body.data[0].id 596 const server1ThreadId = resThread2.body.data[0].id
597 await checkCommentMention(baseParams, uuid, server1ThreadId, server1ThreadId, 'super root 2 name', 'presence') 597 await checkCommentMention(baseParams, uuid, server1ThreadId, server1ThreadId, 'super root 2 name', 'presence')
598 598
599 const text2 = `@user_1@localhost:${servers[ 0 ].port} hello 2 @root@localhost:${servers[ 0 ].port}` 599 const text2 = `@user_1@localhost:${servers[0].port} hello 2 @root@localhost:${servers[0].port}`
600 await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, server2ThreadId, text2) 600 await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, server2ThreadId, text2)
601 601
602 await waitJobs(servers) 602 await waitJobs(servers)
@@ -611,7 +611,7 @@ describe('Test users notifications', function () {
611 }) 611 })
612 }) 612 })
613 613
614 describe('Video abuse for moderators notification' , function () { 614 describe('Video abuse for moderators notification', function () {
615 let baseParams: CheckerBaseParams 615 let baseParams: CheckerBaseParams
616 616
617 before(() => { 617 before(() => {
@@ -722,7 +722,7 @@ describe('Test users notifications', function () {
722 await uploadVideoByRemoteAccount(servers, { waitTranscoding: false }) 722 await uploadVideoByRemoteAccount(servers, { waitTranscoding: false })
723 await waitJobs(servers) 723 await waitJobs(servers)
724 724
725 const notification = await getLastNotification(servers[ 0 ].url, userAccessToken) 725 const notification = await getLastNotification(servers[0].url, userAccessToken)
726 if (notification) { 726 if (notification) {
727 expect(notification.type).to.not.equal(UserNotificationType.MY_VIDEO_PUBLISHED) 727 expect(notification.type).to.not.equal(UserNotificationType.MY_VIDEO_PUBLISHED)
728 } 728 }
@@ -769,7 +769,7 @@ describe('Test users notifications', function () {
769 this.timeout(70000) 769 this.timeout(70000)
770 770
771 // In 2 seconds 771 // In 2 seconds
772 let updateAt = new Date(new Date().getTime() + 2000) 772 const updateAt = new Date(new Date().getTime() + 2000)
773 773
774 const data = { 774 const data = {
775 privacy: VideoPrivacy.PRIVATE, 775 privacy: VideoPrivacy.PRIVATE,
@@ -787,7 +787,7 @@ describe('Test users notifications', function () {
787 it('Should not send a notification before the video is published', async function () { 787 it('Should not send a notification before the video is published', async function () {
788 this.timeout(20000) 788 this.timeout(20000)
789 789
790 let updateAt = new Date(new Date().getTime() + 1000000) 790 const updateAt = new Date(new Date().getTime() + 1000000)
791 791
792 const data = { 792 const data = {
793 privacy: VideoPrivacy.PRIVATE, 793 privacy: VideoPrivacy.PRIVATE,
@@ -970,8 +970,8 @@ describe('Test users notifications', function () {
970 970
971 describe('New actor follow', function () { 971 describe('New actor follow', function () {
972 let baseParams: CheckerBaseParams 972 let baseParams: CheckerBaseParams
973 let myChannelName = 'super channel name' 973 const myChannelName = 'super channel name'
974 let myUserName = 'super user name' 974 const myUserName = 'super user name'
975 975
976 before(async () => { 976 before(async () => {
977 baseParams = { 977 baseParams = {
@@ -1143,7 +1143,7 @@ describe('Test users notifications', function () {
1143 it('Should send unblacklist but not published/subscription notes after unblacklisted if scheduled update pending', async function () { 1143 it('Should send unblacklist but not published/subscription notes after unblacklisted if scheduled update pending', async function () {
1144 this.timeout(20000) 1144 this.timeout(20000)
1145 1145
1146 let updateAt = new Date(new Date().getTime() + 1000000) 1146 const updateAt = new Date(new Date().getTime() + 1000000)
1147 1147
1148 const name = 'video with auto-blacklist and future schedule ' + uuidv4() 1148 const name = 'video with auto-blacklist and future schedule ' + uuidv4()
1149 1149
@@ -1176,7 +1176,7 @@ describe('Test users notifications', function () {
1176 this.timeout(20000) 1176 this.timeout(20000)
1177 1177
1178 // In 2 seconds 1178 // In 2 seconds
1179 let updateAt = new Date(new Date().getTime() + 2000) 1179 const updateAt = new Date(new Date().getTime() + 2000)
1180 1180
1181 const name = 'video with schedule done and still auto-blacklisted ' + uuidv4() 1181 const name = 'video with schedule done and still auto-blacklisted ' + uuidv4()
1182 1182
@@ -1221,26 +1221,26 @@ describe('Test users notifications', function () {
1221 1221
1222 describe('Mark as read', function () { 1222 describe('Mark as read', function () {
1223 it('Should mark as read some notifications', async function () { 1223 it('Should mark as read some notifications', async function () {
1224 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 2, 3) 1224 const res = await getUserNotifications(servers[0].url, userAccessToken, 2, 3)
1225 const ids = res.body.data.map(n => n.id) 1225 const ids = res.body.data.map(n => n.id)
1226 1226
1227 await markAsReadNotifications(servers[ 0 ].url, userAccessToken, ids) 1227 await markAsReadNotifications(servers[0].url, userAccessToken, ids)
1228 }) 1228 })
1229 1229
1230 it('Should have the notifications marked as read', async function () { 1230 it('Should have the notifications marked as read', async function () {
1231 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10) 1231 const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10)
1232 1232
1233 const notifications = res.body.data as UserNotification[] 1233 const notifications = res.body.data as UserNotification[]
1234 expect(notifications[ 0 ].read).to.be.false 1234 expect(notifications[0].read).to.be.false
1235 expect(notifications[ 1 ].read).to.be.false 1235 expect(notifications[1].read).to.be.false
1236 expect(notifications[ 2 ].read).to.be.true 1236 expect(notifications[2].read).to.be.true
1237 expect(notifications[ 3 ].read).to.be.true 1237 expect(notifications[3].read).to.be.true
1238 expect(notifications[ 4 ].read).to.be.true 1238 expect(notifications[4].read).to.be.true
1239 expect(notifications[ 5 ].read).to.be.false 1239 expect(notifications[5].read).to.be.false
1240 }) 1240 })
1241 1241
1242 it('Should only list read notifications', async function () { 1242 it('Should only list read notifications', async function () {
1243 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, false) 1243 const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10, false)
1244 1244
1245 const notifications = res.body.data as UserNotification[] 1245 const notifications = res.body.data as UserNotification[]
1246 for (const notification of notifications) { 1246 for (const notification of notifications) {
@@ -1249,7 +1249,7 @@ describe('Test users notifications', function () {
1249 }) 1249 })
1250 1250
1251 it('Should only list unread notifications', async function () { 1251 it('Should only list unread notifications', async function () {
1252 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, true) 1252 const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10, true)
1253 1253
1254 const notifications = res.body.data as UserNotification[] 1254 const notifications = res.body.data as UserNotification[]
1255 for (const notification of notifications) { 1255 for (const notification of notifications) {
@@ -1258,9 +1258,9 @@ describe('Test users notifications', function () {
1258 }) 1258 })
1259 1259
1260 it('Should mark as read all notifications', async function () { 1260 it('Should mark as read all notifications', async function () {
1261 await markAsReadAllNotifications(servers[ 0 ].url, userAccessToken) 1261 await markAsReadAllNotifications(servers[0].url, userAccessToken)
1262 1262
1263 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, true) 1263 const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10, true)
1264 1264
1265 expect(res.body.total).to.equal(0) 1265 expect(res.body.total).to.equal(0)
1266 expect(res.body.data).to.have.lengthOf(0) 1266 expect(res.body.data).to.have.lengthOf(0)
diff --git a/server/tests/api/redundancy/index.ts b/server/tests/api/redundancy/index.ts
index 8e69b95a6..5359055b0 100644
--- a/server/tests/api/redundancy/index.ts
+++ b/server/tests/api/redundancy/index.ts
@@ -1 +1,2 @@
1import './redundancy' 1import './redundancy'
2import './manage-redundancy'
diff --git a/server/tests/api/redundancy/manage-redundancy.ts b/server/tests/api/redundancy/manage-redundancy.ts
new file mode 100644
index 000000000..4253124c8
--- /dev/null
+++ b/server/tests/api/redundancy/manage-redundancy.ts
@@ -0,0 +1,373 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import * as chai from 'chai'
4import 'mocha'
5import {
6 cleanupTests,
7 doubleFollow,
8 flushAndRunMultipleServers,
9 getLocalIdByUUID,
10 ServerInfo,
11 setAccessTokensToServers,
12 uploadVideo,
13 uploadVideoAndGetId,
14 waitUntilLog
15} from '../../../../shared/extra-utils'
16import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
17import { addVideoRedundancy, listVideoRedundancies, removeVideoRedundancy, updateRedundancy } from '@shared/extra-utils/server/redundancy'
18import { VideoPrivacy, VideoRedundanciesTarget, VideoRedundancy } from '@shared/models'
19
20const expect = chai.expect
21
22describe('Test manage videos redundancy', function () {
23 const targets: VideoRedundanciesTarget[] = [ 'my-videos', 'remote-videos' ]
24
25 let servers: ServerInfo[]
26 let video1Server2UUID: string
27 let video2Server2UUID: string
28 let redundanciesToRemove: number[] = []
29
30 before(async function () {
31 this.timeout(120000)
32
33 const config = {
34 transcoding: {
35 hls: {
36 enabled: true
37 }
38 },
39 redundancy: {
40 videos: {
41 check_interval: '1 second',
42 strategies: [
43 {
44 strategy: 'recently-added',
45 min_lifetime: '1 hour',
46 size: '10MB',
47 min_views: 0
48 }
49 ]
50 }
51 }
52 }
53 servers = await flushAndRunMultipleServers(3, config)
54
55 // Get the access tokens
56 await setAccessTokensToServers(servers)
57
58 {
59 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 1 server 2' })
60 video1Server2UUID = res.body.video.uuid
61 }
62
63 {
64 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 2 server 2' })
65 video2Server2UUID = res.body.video.uuid
66 }
67
68 await waitJobs(servers)
69
70 // Server 1 and server 2 follow each other
71 await doubleFollow(servers[0], servers[1])
72 await updateRedundancy(servers[0].url, servers[0].accessToken, servers[1].host, true)
73
74 await waitJobs(servers)
75 })
76
77 it('Should not have redundancies on server 3', async function () {
78 for (const target of targets) {
79 const res = await listVideoRedundancies({
80 url: servers[2].url,
81 accessToken: servers[2].accessToken,
82 target
83 })
84
85 expect(res.body.total).to.equal(0)
86 expect(res.body.data).to.have.lengthOf(0)
87 }
88 })
89
90 it('Should not have "remote-videos" redundancies on server 2', async function () {
91 this.timeout(120000)
92
93 await waitJobs(servers)
94 await waitUntilLog(servers[0], 'Duplicated ', 10)
95 await waitJobs(servers)
96
97 const res = await listVideoRedundancies({
98 url: servers[1].url,
99 accessToken: servers[1].accessToken,
100 target: 'remote-videos'
101 })
102
103 expect(res.body.total).to.equal(0)
104 expect(res.body.data).to.have.lengthOf(0)
105 })
106
107 it('Should have "my-videos" redundancies on server 2', async function () {
108 this.timeout(120000)
109
110 const res = await listVideoRedundancies({
111 url: servers[1].url,
112 accessToken: servers[1].accessToken,
113 target: 'my-videos'
114 })
115
116 expect(res.body.total).to.equal(2)
117
118 const videos = res.body.data as VideoRedundancy[]
119 expect(videos).to.have.lengthOf(2)
120
121 const videos1 = videos.find(v => v.uuid === video1Server2UUID)
122 const videos2 = videos.find(v => v.uuid === video2Server2UUID)
123
124 expect(videos1.name).to.equal('video 1 server 2')
125 expect(videos2.name).to.equal('video 2 server 2')
126
127 expect(videos1.redundancies.files).to.have.lengthOf(4)
128 expect(videos1.redundancies.streamingPlaylists).to.have.lengthOf(1)
129
130 const redundancies = videos1.redundancies.files.concat(videos1.redundancies.streamingPlaylists)
131
132 for (const r of redundancies) {
133 expect(r.strategy).to.be.null
134 expect(r.fileUrl).to.exist
135 expect(r.createdAt).to.exist
136 expect(r.updatedAt).to.exist
137 expect(r.expiresOn).to.exist
138 }
139 })
140
141 it('Should not have "my-videos" redundancies on server 1', async function () {
142 const res = await listVideoRedundancies({
143 url: servers[0].url,
144 accessToken: servers[0].accessToken,
145 target: 'my-videos'
146 })
147
148 expect(res.body.total).to.equal(0)
149 expect(res.body.data).to.have.lengthOf(0)
150 })
151
152 it('Should have "remote-videos" redundancies on server 1', async function () {
153 this.timeout(120000)
154
155 const res = await listVideoRedundancies({
156 url: servers[0].url,
157 accessToken: servers[0].accessToken,
158 target: 'remote-videos'
159 })
160
161 expect(res.body.total).to.equal(2)
162
163 const videos = res.body.data as VideoRedundancy[]
164 expect(videos).to.have.lengthOf(2)
165
166 const videos1 = videos.find(v => v.uuid === video1Server2UUID)
167 const videos2 = videos.find(v => v.uuid === video2Server2UUID)
168
169 expect(videos1.name).to.equal('video 1 server 2')
170 expect(videos2.name).to.equal('video 2 server 2')
171
172 expect(videos1.redundancies.files).to.have.lengthOf(4)
173 expect(videos1.redundancies.streamingPlaylists).to.have.lengthOf(1)
174
175 const redundancies = videos1.redundancies.files.concat(videos1.redundancies.streamingPlaylists)
176
177 for (const r of redundancies) {
178 expect(r.strategy).to.equal('recently-added')
179 expect(r.fileUrl).to.exist
180 expect(r.createdAt).to.exist
181 expect(r.updatedAt).to.exist
182 expect(r.expiresOn).to.exist
183 }
184 })
185
186 it('Should correctly paginate and sort results', async function () {
187 {
188 const res = await listVideoRedundancies({
189 url: servers[0].url,
190 accessToken: servers[0].accessToken,
191 target: 'remote-videos',
192 sort: 'name',
193 start: 0,
194 count: 2
195 })
196
197 const videos = res.body.data
198 expect(videos[0].name).to.equal('video 1 server 2')
199 expect(videos[1].name).to.equal('video 2 server 2')
200 }
201
202 {
203 const res = await listVideoRedundancies({
204 url: servers[0].url,
205 accessToken: servers[0].accessToken,
206 target: 'remote-videos',
207 sort: '-name',
208 start: 0,
209 count: 2
210 })
211
212 const videos = res.body.data
213 expect(videos[0].name).to.equal('video 2 server 2')
214 expect(videos[1].name).to.equal('video 1 server 2')
215 }
216
217 {
218 const res = await listVideoRedundancies({
219 url: servers[0].url,
220 accessToken: servers[0].accessToken,
221 target: 'remote-videos',
222 sort: '-name',
223 start: 1,
224 count: 1
225 })
226
227 const videos = res.body.data
228 expect(videos[0].name).to.equal('video 1 server 2')
229 }
230 })
231
232 it('Should manually add a redundancy and list it', async function () {
233 this.timeout(120000)
234
235 const uuid = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video 3 server 2', privacy: VideoPrivacy.UNLISTED })).uuid
236 await waitJobs(servers)
237 const videoId = await getLocalIdByUUID(servers[0].url, uuid)
238
239 await addVideoRedundancy({
240 url: servers[0].url,
241 accessToken: servers[0].accessToken,
242 videoId
243 })
244
245 await waitJobs(servers)
246 await waitUntilLog(servers[0], 'Duplicated ', 15)
247 await waitJobs(servers)
248
249 {
250 const res = await listVideoRedundancies({
251 url: servers[0].url,
252 accessToken: servers[0].accessToken,
253 target: 'remote-videos',
254 sort: '-name',
255 start: 0,
256 count: 5
257 })
258
259 const videos = res.body.data
260 expect(videos[0].name).to.equal('video 3 server 2')
261
262 const video = videos[0]
263 expect(video.redundancies.files).to.have.lengthOf(4)
264 expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1)
265
266 const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists)
267
268 for (const r of redundancies) {
269 redundanciesToRemove.push(r.id)
270
271 expect(r.strategy).to.equal('manual')
272 expect(r.fileUrl).to.exist
273 expect(r.createdAt).to.exist
274 expect(r.updatedAt).to.exist
275 expect(r.expiresOn).to.be.null
276 }
277 }
278
279 const res = await listVideoRedundancies({
280 url: servers[1].url,
281 accessToken: servers[1].accessToken,
282 target: 'my-videos',
283 sort: '-name',
284 start: 0,
285 count: 5
286 })
287
288 const videos = res.body.data
289 expect(videos[0].name).to.equal('video 3 server 2')
290
291 const video = videos[0]
292 expect(video.redundancies.files).to.have.lengthOf(4)
293 expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1)
294
295 const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists)
296
297 for (const r of redundancies) {
298 expect(r.strategy).to.be.null
299 expect(r.fileUrl).to.exist
300 expect(r.createdAt).to.exist
301 expect(r.updatedAt).to.exist
302 expect(r.expiresOn).to.be.null
303 }
304 })
305
306 it('Should manually remove a redundancy and remove it from the list', async function () {
307 this.timeout(120000)
308
309 for (const redundancyId of redundanciesToRemove) {
310 await removeVideoRedundancy({
311 url: servers[0].url,
312 accessToken: servers[0].accessToken,
313 redundancyId
314 })
315 }
316
317 {
318 const res = await listVideoRedundancies({
319 url: servers[0].url,
320 accessToken: servers[0].accessToken,
321 target: 'remote-videos',
322 sort: '-name',
323 start: 0,
324 count: 5
325 })
326
327 const videos = res.body.data
328 expect(videos).to.have.lengthOf(2)
329
330 expect(videos[0].name).to.equal('video 2 server 2')
331
332 redundanciesToRemove = []
333 const video = videos[0]
334 expect(video.redundancies.files).to.have.lengthOf(4)
335 expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1)
336
337 const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists)
338
339 for (const r of redundancies) {
340 redundanciesToRemove.push(r.id)
341 }
342 }
343 })
344
345 it('Should remove another (auto) redundancy', async function () {
346 {
347 for (const redundancyId of redundanciesToRemove) {
348 await removeVideoRedundancy({
349 url: servers[0].url,
350 accessToken: servers[0].accessToken,
351 redundancyId
352 })
353 }
354
355 const res = await listVideoRedundancies({
356 url: servers[0].url,
357 accessToken: servers[0].accessToken,
358 target: 'remote-videos',
359 sort: '-name',
360 start: 0,
361 count: 5
362 })
363
364 const videos = res.body.data
365 expect(videos[0].name).to.equal('video 1 server 2')
366 expect(videos).to.have.lengthOf(1)
367 }
368 })
369
370 after(async function () {
371 await cleanupTests(servers)
372 })
373})
diff --git a/server/tests/api/redundancy/redundancy.ts b/server/tests/api/redundancy/redundancy.ts
index 1cdf93aa1..c5037a541 100644
--- a/server/tests/api/redundancy/redundancy.ts
+++ b/server/tests/api/redundancy/redundancy.ts
@@ -1,11 +1,12 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { VideoDetails } from '../../../../shared/models/videos' 5import { VideoDetails } from '../../../../shared/models/videos'
6import { 6import {
7 checkSegmentHash, 7 checkSegmentHash,
8 checkVideoFilesWereRemoved, cleanupTests, 8 checkVideoFilesWereRemoved,
9 cleanupTests,
9 doubleFollow, 10 doubleFollow,
10 flushAndRunMultipleServers, 11 flushAndRunMultipleServers,
11 getFollowingListPaginationAndSort, 12 getFollowingListPaginationAndSort,
@@ -28,11 +29,16 @@ import {
28import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 29import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
29 30
30import * as magnetUtil from 'magnet-uri' 31import * as magnetUtil from 'magnet-uri'
31import { updateRedundancy } from '../../../../shared/extra-utils/server/redundancy' 32import {
33 addVideoRedundancy,
34 listVideoRedundancies,
35 removeVideoRedundancy,
36 updateRedundancy
37} from '../../../../shared/extra-utils/server/redundancy'
32import { ActorFollow } from '../../../../shared/models/actors' 38import { ActorFollow } from '../../../../shared/models/actors'
33import { readdir } from 'fs-extra' 39import { readdir } from 'fs-extra'
34import { join } from 'path' 40import { join } from 'path'
35import { VideoRedundancyStrategy } from '../../../../shared/models/redundancy' 41import { VideoRedundancy, VideoRedundancyStrategy, VideoRedundancyStrategyWithManual } from '../../../../shared/models/redundancy'
36import { getStats } from '../../../../shared/extra-utils/server/stats' 42import { getStats } from '../../../../shared/extra-utils/server/stats'
37import { ServerStats } from '../../../../shared/models/server/server-stats.model' 43import { ServerStats } from '../../../../shared/models/server/server-stats.model'
38 44
@@ -40,6 +46,7 @@ const expect = chai.expect
40 46
41let servers: ServerInfo[] = [] 47let servers: ServerInfo[] = []
42let video1Server2UUID: string 48let video1Server2UUID: string
49let video1Server2Id: number
43 50
44function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: number } }, baseWebseeds: string[], server: ServerInfo) { 51function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: number } }, baseWebseeds: string[], server: ServerInfo) {
45 const parsed = magnetUtil.decode(file.magnetUri) 52 const parsed = magnetUtil.decode(file.magnetUri)
@@ -52,7 +59,19 @@ function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: numbe
52 expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length) 59 expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length)
53} 60}
54 61
55async function flushAndRunServers (strategy: VideoRedundancyStrategy, additionalParams: any = {}) { 62async function flushAndRunServers (strategy: VideoRedundancyStrategy | null, additionalParams: any = {}) {
63 const strategies: any[] = []
64
65 if (strategy !== null) {
66 strategies.push(
67 immutableAssign({
68 min_lifetime: '1 hour',
69 strategy: strategy,
70 size: '400KB'
71 }, additionalParams)
72 )
73 }
74
56 const config = { 75 const config = {
57 transcoding: { 76 transcoding: {
58 hls: { 77 hls: {
@@ -62,36 +81,32 @@ async function flushAndRunServers (strategy: VideoRedundancyStrategy, additional
62 redundancy: { 81 redundancy: {
63 videos: { 82 videos: {
64 check_interval: '5 seconds', 83 check_interval: '5 seconds',
65 strategies: [ 84 strategies
66 immutableAssign({
67 min_lifetime: '1 hour',
68 strategy: strategy,
69 size: '400KB'
70 }, additionalParams)
71 ]
72 } 85 }
73 } 86 }
74 } 87 }
88
75 servers = await flushAndRunMultipleServers(3, config) 89 servers = await flushAndRunMultipleServers(3, config)
76 90
77 // Get the access tokens 91 // Get the access tokens
78 await setAccessTokensToServers(servers) 92 await setAccessTokensToServers(servers)
79 93
80 { 94 {
81 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 1 server 2' }) 95 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 1 server 2' })
82 video1Server2UUID = res.body.video.uuid 96 video1Server2UUID = res.body.video.uuid
97 video1Server2Id = res.body.video.id
83 98
84 await viewVideo(servers[ 1 ].url, video1Server2UUID) 99 await viewVideo(servers[1].url, video1Server2UUID)
85 } 100 }
86 101
87 await waitJobs(servers) 102 await waitJobs(servers)
88 103
89 // Server 1 and server 2 follow each other 104 // Server 1 and server 2 follow each other
90 await doubleFollow(servers[ 0 ], servers[ 1 ]) 105 await doubleFollow(servers[0], servers[1])
91 // Server 1 and server 3 follow each other 106 // Server 1 and server 3 follow each other
92 await doubleFollow(servers[ 0 ], servers[ 2 ]) 107 await doubleFollow(servers[0], servers[2])
93 // Server 2 and server 3 follow each other 108 // Server 2 and server 3 follow each other
94 await doubleFollow(servers[ 1 ], servers[ 2 ]) 109 await doubleFollow(servers[1], servers[2])
95 110
96 await waitJobs(servers) 111 await waitJobs(servers)
97} 112}
@@ -100,7 +115,7 @@ async function check1WebSeed (videoUUID?: string) {
100 if (!videoUUID) videoUUID = video1Server2UUID 115 if (!videoUUID) videoUUID = video1Server2UUID
101 116
102 const webseeds = [ 117 const webseeds = [
103 `http://localhost:${servers[ 1 ].port}/static/webseed/${videoUUID}` 118 `http://localhost:${servers[1].port}/static/webseed/${videoUUID}`
104 ] 119 ]
105 120
106 for (const server of servers) { 121 for (const server of servers) {
@@ -118,8 +133,8 @@ async function check2Webseeds (videoUUID?: string) {
118 if (!videoUUID) videoUUID = video1Server2UUID 133 if (!videoUUID) videoUUID = video1Server2UUID
119 134
120 const webseeds = [ 135 const webseeds = [
121 `http://localhost:${servers[ 0 ].port}/static/redundancy/${videoUUID}`, 136 `http://localhost:${servers[0].port}/static/redundancy/${videoUUID}`,
122 `http://localhost:${servers[ 1 ].port}/static/webseed/${videoUUID}` 137 `http://localhost:${servers[1].port}/static/webseed/${videoUUID}`
123 ] 138 ]
124 139
125 for (const server of servers) { 140 for (const server of servers) {
@@ -216,41 +231,50 @@ async function check1PlaylistRedundancies (videoUUID?: string) {
216 } 231 }
217} 232}
218 233
219async function checkStatsWith2Webseed (strategy: VideoRedundancyStrategy) { 234async function checkStatsGlobal (strategy: VideoRedundancyStrategyWithManual) {
235 let totalSize: number = null
236 let statsLength = 1
237
238 if (strategy !== 'manual') {
239 totalSize = 409600
240 statsLength = 2
241 }
242
220 const res = await getStats(servers[0].url) 243 const res = await getStats(servers[0].url)
221 const data: ServerStats = res.body 244 const data: ServerStats = res.body
222 245
223 expect(data.videosRedundancy).to.have.lengthOf(1) 246 expect(data.videosRedundancy).to.have.lengthOf(statsLength)
224 const stat = data.videosRedundancy[0]
225 247
248 const stat = data.videosRedundancy[0]
226 expect(stat.strategy).to.equal(strategy) 249 expect(stat.strategy).to.equal(strategy)
227 expect(stat.totalSize).to.equal(409600) 250 expect(stat.totalSize).to.equal(totalSize)
251
252 return stat
253}
254
255async function checkStatsWith2Webseed (strategy: VideoRedundancyStrategyWithManual) {
256 const stat = await checkStatsGlobal(strategy)
257
228 expect(stat.totalUsed).to.be.at.least(1).and.below(409601) 258 expect(stat.totalUsed).to.be.at.least(1).and.below(409601)
229 expect(stat.totalVideoFiles).to.equal(4) 259 expect(stat.totalVideoFiles).to.equal(4)
230 expect(stat.totalVideos).to.equal(1) 260 expect(stat.totalVideos).to.equal(1)
231} 261}
232 262
233async function checkStatsWith1Webseed (strategy: VideoRedundancyStrategy) { 263async function checkStatsWith1Webseed (strategy: VideoRedundancyStrategyWithManual) {
234 const res = await getStats(servers[0].url) 264 const stat = await checkStatsGlobal(strategy)
235 const data: ServerStats = res.body
236 265
237 expect(data.videosRedundancy).to.have.lengthOf(1)
238
239 const stat = data.videosRedundancy[0]
240 expect(stat.strategy).to.equal(strategy)
241 expect(stat.totalSize).to.equal(409600)
242 expect(stat.totalUsed).to.equal(0) 266 expect(stat.totalUsed).to.equal(0)
243 expect(stat.totalVideoFiles).to.equal(0) 267 expect(stat.totalVideoFiles).to.equal(0)
244 expect(stat.totalVideos).to.equal(0) 268 expect(stat.totalVideos).to.equal(0)
245} 269}
246 270
247async function enableRedundancyOnServer1 () { 271async function enableRedundancyOnServer1 () {
248 await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, true) 272 await updateRedundancy(servers[0].url, servers[0].accessToken, servers[1].host, true)
249 273
250 const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: '-createdAt' }) 274 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: '-createdAt' })
251 const follows: ActorFollow[] = res.body.data 275 const follows: ActorFollow[] = res.body.data
252 const server2 = follows.find(f => f.following.host === `localhost:${servers[ 1 ].port}`) 276 const server2 = follows.find(f => f.following.host === `localhost:${servers[1].port}`)
253 const server3 = follows.find(f => f.following.host === `localhost:${servers[ 2 ].port}`) 277 const server3 = follows.find(f => f.following.host === `localhost:${servers[2].port}`)
254 278
255 expect(server3).to.not.be.undefined 279 expect(server3).to.not.be.undefined
256 expect(server3.following.hostRedundancyAllowed).to.be.false 280 expect(server3.following.hostRedundancyAllowed).to.be.false
@@ -260,12 +284,12 @@ async function enableRedundancyOnServer1 () {
260} 284}
261 285
262async function disableRedundancyOnServer1 () { 286async function disableRedundancyOnServer1 () {
263 await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, false) 287 await updateRedundancy(servers[0].url, servers[0].accessToken, servers[1].host, false)
264 288
265 const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: '-createdAt' }) 289 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: '-createdAt' })
266 const follows: ActorFollow[] = res.body.data 290 const follows: ActorFollow[] = res.body.data
267 const server2 = follows.find(f => f.following.host === `localhost:${servers[ 1 ].port}`) 291 const server2 = follows.find(f => f.following.host === `localhost:${servers[1].port}`)
268 const server3 = follows.find(f => f.following.host === `localhost:${servers[ 2 ].port}`) 292 const server3 = follows.find(f => f.following.host === `localhost:${servers[2].port}`)
269 293
270 expect(server3).to.not.be.undefined 294 expect(server3).to.not.be.undefined
271 expect(server3.following.hostRedundancyAllowed).to.be.false 295 expect(server3.following.hostRedundancyAllowed).to.be.false
@@ -410,8 +434,8 @@ describe('Test videos redundancy', function () {
410 it('Should view 2 times the first video to have > min_views config', async function () { 434 it('Should view 2 times the first video to have > min_views config', async function () {
411 this.timeout(80000) 435 this.timeout(80000)
412 436
413 await viewVideo(servers[ 0 ].url, video1Server2UUID) 437 await viewVideo(servers[0].url, video1Server2UUID)
414 await viewVideo(servers[ 2 ].url, video1Server2UUID) 438 await viewVideo(servers[2].url, video1Server2UUID)
415 439
416 await wait(10000) 440 await wait(10000)
417 await waitJobs(servers) 441 await waitJobs(servers)
@@ -446,6 +470,74 @@ describe('Test videos redundancy', function () {
446 }) 470 })
447 }) 471 })
448 472
473 describe('With manual strategy', function () {
474 before(function () {
475 this.timeout(120000)
476
477 return flushAndRunServers(null)
478 })
479
480 it('Should have 1 webseed on the first video', async function () {
481 await check1WebSeed()
482 await check0PlaylistRedundancies()
483 await checkStatsWith1Webseed('manual')
484 })
485
486 it('Should create a redundancy on first video', async function () {
487 await addVideoRedundancy({
488 url: servers[0].url,
489 accessToken: servers[0].accessToken,
490 videoId: video1Server2Id
491 })
492 })
493
494 it('Should have 2 webseeds on the first video', async function () {
495 this.timeout(80000)
496
497 await waitJobs(servers)
498 await waitUntilLog(servers[0], 'Duplicated ', 5)
499 await waitJobs(servers)
500
501 await check2Webseeds()
502 await check1PlaylistRedundancies()
503 await checkStatsWith2Webseed('manual')
504 })
505
506 it('Should manually remove redundancies on server 1 and remove duplicated videos', async function () {
507 this.timeout(80000)
508
509 const res = await listVideoRedundancies({
510 url: servers[0].url,
511 accessToken: servers[0].accessToken,
512 target: 'remote-videos'
513 })
514
515 const videos = res.body.data as VideoRedundancy[]
516 expect(videos).to.have.lengthOf(1)
517
518 const video = videos[0]
519 for (const r of video.redundancies.files.concat(video.redundancies.streamingPlaylists)) {
520 await removeVideoRedundancy({
521 url: servers[0].url,
522 accessToken: servers[0].accessToken,
523 redundancyId: r.id
524 })
525 }
526
527 await waitJobs(servers)
528 await wait(5000)
529
530 await check1WebSeed()
531 await check0PlaylistRedundancies()
532
533 await checkVideoFilesWereRemoved(video1Server2UUID, servers[0].serverNumber, [ 'videos' ])
534 })
535
536 after(async function () {
537 await cleanupTests(servers)
538 })
539 })
540
449 describe('Test expiration', function () { 541 describe('Test expiration', function () {
450 const strategy = 'recently-added' 542 const strategy = 'recently-added'
451 543
@@ -528,7 +620,7 @@ describe('Test videos redundancy', function () {
528 await check1PlaylistRedundancies() 620 await check1PlaylistRedundancies()
529 await checkStatsWith2Webseed(strategy) 621 await checkStatsWith2Webseed(strategy)
530 622
531 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 2 server 2' }) 623 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 2 server 2' })
532 video2Server2UUID = res.body.video.uuid 624 video2Server2UUID = res.body.video.uuid
533 }) 625 })
534 626
@@ -560,8 +652,8 @@ describe('Test videos redundancy', function () {
560 652
561 await waitJobs(servers) 653 await waitJobs(servers)
562 654
563 killallServers([ servers[ 0 ] ]) 655 killallServers([ servers[0] ])
564 await reRunServer(servers[ 0 ], { 656 await reRunServer(servers[0], {
565 redundancy: { 657 redundancy: {
566 videos: { 658 videos: {
567 check_interval: '1 second', 659 check_interval: '1 second',
diff --git a/server/tests/api/search/search-activitypub-video-channels.ts b/server/tests/api/search/search-activitypub-video-channels.ts
index d5f0a5457..d7e3ed5be 100644
--- a/server/tests/api/search/search-activitypub-video-channels.ts
+++ b/server/tests/api/search/search-activitypub-video-channels.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -39,7 +39,7 @@ describe('Test ActivityPub video channels search', function () {
39 await setAccessTokensToServers(servers) 39 await setAccessTokensToServers(servers)
40 40
41 { 41 {
42 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: 'user1_server1', password: 'password' }) 42 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: 'user1_server1', password: 'password' })
43 const channel = { 43 const channel = {
44 name: 'channel1_server1', 44 name: 'channel1_server1',
45 displayName: 'Channel 1 server 1' 45 displayName: 'Channel 1 server 1'
@@ -49,7 +49,7 @@ describe('Test ActivityPub video channels search', function () {
49 49
50 { 50 {
51 const user = { username: 'user1_server2', password: 'password' } 51 const user = { username: 'user1_server2', password: 'password' }
52 await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password }) 52 await createUser({ url: servers[1].url, accessToken: servers[1].accessToken, username: user.username, password: user.password })
53 userServer2Token = await userLogin(servers[1], user) 53 userServer2Token = await userLogin(servers[1], user)
54 54
55 const channel = { 55 const channel = {
@@ -70,8 +70,8 @@ describe('Test ActivityPub video channels search', function () {
70 this.timeout(15000) 70 this.timeout(15000)
71 71
72 { 72 {
73 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server3' 73 const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server3'
74 const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) 74 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
75 75
76 expect(res.body.total).to.equal(0) 76 expect(res.body.total).to.equal(0)
77 expect(res.body.data).to.be.an('array') 77 expect(res.body.data).to.be.an('array')
@@ -80,7 +80,7 @@ describe('Test ActivityPub video channels search', function () {
80 80
81 { 81 {
82 // Without token 82 // Without token
83 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2' 83 const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2'
84 const res = await searchVideoChannel(servers[0].url, search) 84 const res = await searchVideoChannel(servers[0].url, search)
85 85
86 expect(res.body.total).to.equal(0) 86 expect(res.body.total).to.equal(0)
@@ -91,35 +91,35 @@ describe('Test ActivityPub video channels search', function () {
91 91
92 it('Should search a local video channel', async function () { 92 it('Should search a local video channel', async function () {
93 const searches = [ 93 const searches = [
94 'http://localhost:' + servers[ 0 ].port + '/video-channels/channel1_server1', 94 'http://localhost:' + servers[0].port + '/video-channels/channel1_server1',
95 'channel1_server1@localhost:' + servers[ 0 ].port 95 'channel1_server1@localhost:' + servers[0].port
96 ] 96 ]
97 97
98 for (const search of searches) { 98 for (const search of searches) {
99 const res = await searchVideoChannel(servers[ 0 ].url, search) 99 const res = await searchVideoChannel(servers[0].url, search)
100 100
101 expect(res.body.total).to.equal(1) 101 expect(res.body.total).to.equal(1)
102 expect(res.body.data).to.be.an('array') 102 expect(res.body.data).to.be.an('array')
103 expect(res.body.data).to.have.lengthOf(1) 103 expect(res.body.data).to.have.lengthOf(1)
104 expect(res.body.data[ 0 ].name).to.equal('channel1_server1') 104 expect(res.body.data[0].name).to.equal('channel1_server1')
105 expect(res.body.data[ 0 ].displayName).to.equal('Channel 1 server 1') 105 expect(res.body.data[0].displayName).to.equal('Channel 1 server 1')
106 } 106 }
107 }) 107 })
108 108
109 it('Should search a remote video channel with URL or handle', async function () { 109 it('Should search a remote video channel with URL or handle', async function () {
110 const searches = [ 110 const searches = [
111 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2', 111 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2',
112 'channel1_server2@localhost:' + servers[ 1 ].port 112 'channel1_server2@localhost:' + servers[1].port
113 ] 113 ]
114 114
115 for (const search of searches) { 115 for (const search of searches) {
116 const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) 116 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
117 117
118 expect(res.body.total).to.equal(1) 118 expect(res.body.total).to.equal(1)
119 expect(res.body.data).to.be.an('array') 119 expect(res.body.data).to.be.an('array')
120 expect(res.body.data).to.have.lengthOf(1) 120 expect(res.body.data).to.have.lengthOf(1)
121 expect(res.body.data[ 0 ].name).to.equal('channel1_server2') 121 expect(res.body.data[0].name).to.equal('channel1_server2')
122 expect(res.body.data[ 0 ].displayName).to.equal('Channel 1 server 2') 122 expect(res.body.data[0].displayName).to.equal('Channel 1 server 2')
123 } 123 }
124 }) 124 })
125 125
@@ -137,13 +137,13 @@ describe('Test ActivityPub video channels search', function () {
137 137
138 await waitJobs(servers) 138 await waitJobs(servers)
139 139
140 const res = await getVideoChannelVideos(servers[0].url, null, 'channel1_server2@localhost:' + servers[ 1 ].port, 0, 5) 140 const res = await getVideoChannelVideos(servers[0].url, null, 'channel1_server2@localhost:' + servers[1].port, 0, 5)
141 expect(res.body.total).to.equal(0) 141 expect(res.body.total).to.equal(0)
142 expect(res.body.data).to.have.lengthOf(0) 142 expect(res.body.data).to.have.lengthOf(0)
143 }) 143 })
144 144
145 it('Should list video channel videos of server 2 with token', async function () { 145 it('Should list video channel videos of server 2 with token', async function () {
146 const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'channel1_server2@localhost:' + servers[ 1 ].port, 0, 5) 146 const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'channel1_server2@localhost:' + servers[1].port, 0, 5)
147 147
148 expect(res.body.total).to.equal(1) 148 expect(res.body.total).to.equal(1)
149 expect(res.body.data[0].name).to.equal('video 1 server 2') 149 expect(res.body.data[0].name).to.equal('video 1 server 2')
@@ -159,7 +159,7 @@ describe('Test ActivityPub video channels search', function () {
159 // Expire video channel 159 // Expire video channel
160 await wait(10000) 160 await wait(10000)
161 161
162 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2' 162 const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2'
163 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken) 163 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
164 expect(res.body.total).to.equal(1) 164 expect(res.body.total).to.equal(1)
165 expect(res.body.data).to.have.lengthOf(1) 165 expect(res.body.data).to.have.lengthOf(1)
@@ -182,12 +182,12 @@ describe('Test ActivityPub video channels search', function () {
182 // Expire video channel 182 // Expire video channel
183 await wait(10000) 183 await wait(10000)
184 184
185 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2' 185 const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2'
186 await searchVideoChannel(servers[0].url, search, servers[0].accessToken) 186 await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
187 187
188 await waitJobs(servers) 188 await waitJobs(servers)
189 189
190 const videoChannelName = 'channel1_server2@localhost:' + servers[ 1 ].port 190 const videoChannelName = 'channel1_server2@localhost:' + servers[1].port
191 const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, videoChannelName, 0, 5, '-createdAt') 191 const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, videoChannelName, 0, 5, '-createdAt')
192 192
193 expect(res.body.total).to.equal(2) 193 expect(res.body.total).to.equal(2)
@@ -204,7 +204,7 @@ describe('Test ActivityPub video channels search', function () {
204 // Expire video 204 // Expire video
205 await wait(10000) 205 await wait(10000)
206 206
207 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2' 207 const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2'
208 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken) 208 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
209 expect(res.body.total).to.equal(0) 209 expect(res.body.total).to.equal(0)
210 expect(res.body.data).to.have.lengthOf(0) 210 expect(res.body.data).to.have.lengthOf(0)
diff --git a/server/tests/api/search/search-activitypub-videos.ts b/server/tests/api/search/search-activitypub-videos.ts
index dbfefadda..c62dfca0d 100644
--- a/server/tests/api/search/search-activitypub-videos.ts
+++ b/server/tests/api/search/search-activitypub-videos.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -34,12 +34,12 @@ describe('Test ActivityPub videos search', function () {
34 await setAccessTokensToServers(servers) 34 await setAccessTokensToServers(servers)
35 35
36 { 36 {
37 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video 1 on server 1' }) 37 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video 1 on server 1' })
38 videoServer1UUID = res.body.video.uuid 38 videoServer1UUID = res.body.video.uuid
39 } 39 }
40 40
41 { 41 {
42 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 1 on server 2' }) 42 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 1 on server 2' })
43 videoServer2UUID = res.body.video.uuid 43 videoServer2UUID = res.body.video.uuid
44 } 44 }
45 45
@@ -49,7 +49,7 @@ describe('Test ActivityPub videos search', function () {
49 it('Should not find a remote video', async function () { 49 it('Should not find a remote video', async function () {
50 { 50 {
51 const search = 'http://localhost:' + servers[1].port + '/videos/watch/43' 51 const search = 'http://localhost:' + servers[1].port + '/videos/watch/43'
52 const res = await searchVideoWithToken(servers[ 0 ].url, search, servers[ 0 ].accessToken) 52 const res = await searchVideoWithToken(servers[0].url, search, servers[0].accessToken)
53 53
54 expect(res.body.total).to.equal(0) 54 expect(res.body.total).to.equal(0)
55 expect(res.body.data).to.be.an('array') 55 expect(res.body.data).to.be.an('array')
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts
index 7882d9373..4801fe04a 100644
--- a/server/tests/api/search/search-videos.ts
+++ b/server/tests/api/search/search-videos.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -78,7 +78,7 @@ describe('Test videos search', function () {
78 const attributes5 = immutableAssign(attributes1, { name: attributes1.name + ' - 5', licence: 2, language: undefined }) 78 const attributes5 = immutableAssign(attributes1, { name: attributes1.name + ' - 5', licence: 2, language: undefined })
79 await uploadVideo(server.url, server.accessToken, attributes5) 79 await uploadVideo(server.url, server.accessToken, attributes5)
80 80
81 const attributes6 = immutableAssign(attributes1, { name: attributes1.name + ' - 6', tags: [ 't1', 't2 '] }) 81 const attributes6 = immutableAssign(attributes1, { name: attributes1.name + ' - 6', tags: [ 't1', 't2' ] })
82 await uploadVideo(server.url, server.accessToken, attributes6) 82 await uploadVideo(server.url, server.accessToken, attributes6)
83 83
84 const attributes7 = immutableAssign(attributes1, { 84 const attributes7 = immutableAssign(attributes1, {
@@ -269,16 +269,16 @@ describe('Test videos search', function () {
269 { 269 {
270 const res = await advancedVideosSearch(server.url, query) 270 const res = await advancedVideosSearch(server.url, query)
271 expect(res.body.total).to.equal(2) 271 expect(res.body.total).to.equal(2)
272 expect(res.body.data[ 0 ].name).to.equal('1111 2222 3333 - 3') 272 expect(res.body.data[0].name).to.equal('1111 2222 3333 - 3')
273 expect(res.body.data[ 1 ].name).to.equal('1111 2222 3333 - 4') 273 expect(res.body.data[1].name).to.equal('1111 2222 3333 - 4')
274 } 274 }
275 275
276 { 276 {
277 const res = await advancedVideosSearch(server.url, immutableAssign(query, { languageOneOf: [ 'pl', 'en', '_unknown' ] })) 277 const res = await advancedVideosSearch(server.url, immutableAssign(query, { languageOneOf: [ 'pl', 'en', '_unknown' ] }))
278 expect(res.body.total).to.equal(3) 278 expect(res.body.total).to.equal(3)
279 expect(res.body.data[ 0 ].name).to.equal('1111 2222 3333 - 3') 279 expect(res.body.data[0].name).to.equal('1111 2222 3333 - 3')
280 expect(res.body.data[ 1 ].name).to.equal('1111 2222 3333 - 4') 280 expect(res.body.data[1].name).to.equal('1111 2222 3333 - 4')
281 expect(res.body.data[ 2 ].name).to.equal('1111 2222 3333 - 5') 281 expect(res.body.data[2].name).to.equal('1111 2222 3333 - 5')
282 } 282 }
283 283
284 { 284 {
diff --git a/server/tests/api/server/auto-follows.ts b/server/tests/api/server/auto-follows.ts
index a06f578fc..5f48dc0eb 100644
--- a/server/tests/api/server/auto-follows.ts
+++ b/server/tests/api/server/auto-follows.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -59,9 +59,10 @@ async function server1Follows2 (servers: ServerInfo[]) {
59 59
60async function resetFollows (servers: ServerInfo[]) { 60async function resetFollows (servers: ServerInfo[]) {
61 try { 61 try {
62 await unfollow(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ]) 62 await unfollow(servers[0].url, servers[0].accessToken, servers[1])
63 await unfollow(servers[ 1 ].url, servers[ 1 ].accessToken, servers[ 0 ]) 63 await unfollow(servers[1].url, servers[1].accessToken, servers[0])
64 } catch { /* empty */ } 64 } catch { /* empty */
65 }
65 66
66 await waitJobs(servers) 67 await waitJobs(servers)
67 68
@@ -163,8 +164,8 @@ describe('Test auto follows', function () {
163 await wait(5000) 164 await wait(5000)
164 await waitJobs(servers) 165 await waitJobs(servers)
165 166
166 await checkFollow(servers[ 0 ], servers[ 1 ], false) 167 await checkFollow(servers[0], servers[1], false)
167 await checkFollow(servers[ 1 ], servers[ 0 ], false) 168 await checkFollow(servers[1], servers[0], false)
168 }) 169 })
169 170
170 it('Should auto follow the index', async function () { 171 it('Should auto follow the index', async function () {
@@ -187,7 +188,7 @@ describe('Test auto follows', function () {
187 await wait(5000) 188 await wait(5000)
188 await waitJobs(servers) 189 await waitJobs(servers)
189 190
190 await checkFollow(servers[ 0 ], servers[ 1 ], true) 191 await checkFollow(servers[0], servers[1], true)
191 192
192 await resetFollows(servers) 193 await resetFollows(servers)
193 }) 194 })
@@ -200,8 +201,8 @@ describe('Test auto follows', function () {
200 await wait(5000) 201 await wait(5000)
201 await waitJobs(servers) 202 await waitJobs(servers)
202 203
203 await checkFollow(servers[ 0 ], servers[ 1 ], false) 204 await checkFollow(servers[0], servers[1], false)
204 await checkFollow(servers[ 0 ], servers[ 2 ], true) 205 await checkFollow(servers[0], servers[2], true)
205 }) 206 })
206 }) 207 })
207 208
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts
index cf99e5c0a..642525455 100644
--- a/server/tests/api/server/config.ts
+++ b/server/tests/api/server/config.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
@@ -11,11 +11,14 @@ import {
11 getAbout, 11 getAbout,
12 getConfig, 12 getConfig,
13 getCustomConfig, 13 getCustomConfig,
14 killallServers, parallelTests, 14 killallServers,
15 parallelTests,
15 registerUser, 16 registerUser,
16 reRunServer, ServerInfo, 17 reRunServer,
18 ServerInfo,
17 setAccessTokensToServers, 19 setAccessTokensToServers,
18 updateCustomConfig, uploadVideo 20 updateCustomConfig,
21 uploadVideo
19} from '../../../../shared/extra-utils' 22} from '../../../../shared/extra-utils'
20import { ServerConfig } from '../../../../shared/models' 23import { ServerConfig } from '../../../../shared/models'
21 24
diff --git a/server/tests/api/server/contact-form.ts b/server/tests/api/server/contact-form.ts
index e4e895acb..bd1b0e38a 100644
--- a/server/tests/api/server/contact-form.ts
+++ b/server/tests/api/server/contact-form.ts
@@ -1,16 +1,8 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import { cleanupTests, flushAndRunServer, ServerInfo, setAccessTokensToServers, wait } from '../../../../shared/extra-utils'
6 flushTests,
7 killallServers,
8 flushAndRunServer,
9 ServerInfo,
10 setAccessTokensToServers,
11 wait,
12 cleanupTests
13} from '../../../../shared/extra-utils'
14import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' 6import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
15import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 7import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
16import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form' 8import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form'
diff --git a/server/tests/api/server/email.ts b/server/tests/api/server/email.ts
index c55a221f2..f18859e5d 100644
--- a/server/tests/api/server/email.ts
+++ b/server/tests/api/server/email.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/server/follow-constraints.ts b/server/tests/api/server/follow-constraints.ts
index 46663bf7c..a73440286 100644
--- a/server/tests/api/server/follow-constraints.ts
+++ b/server/tests/api/server/follow-constraints.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -35,11 +35,11 @@ describe('Test follow constraints', function () {
35 await setAccessTokensToServers(servers) 35 await setAccessTokensToServers(servers)
36 36
37 { 37 {
38 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video server 1' }) 38 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video server 1' })
39 video1UUID = res.body.video.uuid 39 video1UUID = res.body.video.uuid
40 } 40 }
41 { 41 {
42 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video server 2' }) 42 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video server 2' })
43 video2UUID = res.body.video.uuid 43 video2UUID = res.body.video.uuid
44 } 44 }
45 45
@@ -47,7 +47,7 @@ describe('Test follow constraints', function () {
47 username: 'user1', 47 username: 'user1',
48 password: 'super_password' 48 password: 'super_password'
49 } 49 }
50 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) 50 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
51 userAccessToken = await userLogin(servers[0], user) 51 userAccessToken = await userLogin(servers[0], user)
52 52
53 await doubleFollow(servers[0], servers[1]) 53 await doubleFollow(servers[0], servers[1])
diff --git a/server/tests/api/server/follows-moderation.ts b/server/tests/api/server/follows-moderation.ts
index 1984c9eb1..cee85cc4b 100644
--- a/server/tests/api/server/follows-moderation.ts
+++ b/server/tests/api/server/follows-moderation.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -24,7 +24,7 @@ const expect = chai.expect
24 24
25async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'accepted') { 25async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'accepted') {
26 { 26 {
27 const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' }) 27 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' })
28 expect(res.body.total).to.equal(1) 28 expect(res.body.total).to.equal(1)
29 29
30 const follow = res.body.data[0] as ActorFollow 30 const follow = res.body.data[0] as ActorFollow
@@ -34,7 +34,7 @@ async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'acc
34 } 34 }
35 35
36 { 36 {
37 const res = await getFollowersListPaginationAndSort({ url: servers[ 1 ].url, start: 0, count: 5, sort: 'createdAt' }) 37 const res = await getFollowersListPaginationAndSort({ url: servers[1].url, start: 0, count: 5, sort: 'createdAt' })
38 expect(res.body.total).to.equal(1) 38 expect(res.body.total).to.equal(1)
39 39
40 const follow = res.body.data[0] as ActorFollow 40 const follow = res.body.data[0] as ActorFollow
@@ -46,12 +46,12 @@ async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'acc
46 46
47async function checkNoFollowers (servers: ServerInfo[]) { 47async function checkNoFollowers (servers: ServerInfo[]) {
48 { 48 {
49 const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' }) 49 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' })
50 expect(res.body.total).to.equal(0) 50 expect(res.body.total).to.equal(0)
51 } 51 }
52 52
53 { 53 {
54 const res = await getFollowersListPaginationAndSort({ url: servers[ 1 ].url, start: 0, count: 5, sort: 'createdAt' }) 54 const res = await getFollowersListPaginationAndSort({ url: servers[1].url, start: 0, count: 5, sort: 'createdAt' })
55 expect(res.body.total).to.equal(0) 55 expect(res.body.total).to.equal(0)
56 } 56 }
57} 57}
@@ -164,17 +164,17 @@ describe('Test follows moderation', function () {
164 await waitJobs(servers) 164 await waitJobs(servers)
165 165
166 { 166 {
167 const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' }) 167 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' })
168 expect(res.body.total).to.equal(2) 168 expect(res.body.total).to.equal(2)
169 } 169 }
170 170
171 { 171 {
172 const res = await getFollowersListPaginationAndSort({ url: servers[ 1 ].url, start: 0, count: 5, sort: 'createdAt' }) 172 const res = await getFollowersListPaginationAndSort({ url: servers[1].url, start: 0, count: 5, sort: 'createdAt' })
173 expect(res.body.total).to.equal(1) 173 expect(res.body.total).to.equal(1)
174 } 174 }
175 175
176 { 176 {
177 const res = await getFollowersListPaginationAndSort({ url: servers[ 2 ].url, start: 0, count: 5, sort: 'createdAt' }) 177 const res = await getFollowersListPaginationAndSort({ url: servers[2].url, start: 0, count: 5, sort: 'createdAt' })
178 expect(res.body.total).to.equal(1) 178 expect(res.body.total).to.equal(1)
179 } 179 }
180 180
@@ -184,7 +184,7 @@ describe('Test follows moderation', function () {
184 await checkServer1And2HasFollowers(servers) 184 await checkServer1And2HasFollowers(servers)
185 185
186 { 186 {
187 const res = await getFollowersListPaginationAndSort({ url: servers[ 2 ].url, start: 0, count: 5, sort: 'createdAt' }) 187 const res = await getFollowersListPaginationAndSort({ url: servers[2].url, start: 0, count: 5, sort: 'createdAt' })
188 expect(res.body.total).to.equal(0) 188 expect(res.body.total).to.equal(0)
189 } 189 }
190 }) 190 })
diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts
index 4ffa9e791..b686af4e4 100644
--- a/server/tests/api/server/follows.ts
+++ b/server/tests/api/server/follows.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -78,14 +78,14 @@ describe('Test follows', function () {
78 }) 78 })
79 79
80 it('Should have 2 followings on server 1', async function () { 80 it('Should have 2 followings on server 1', async function () {
81 let res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 1, sort: 'createdAt' }) 81 let res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 1, sort: 'createdAt' })
82 let follows = res.body.data 82 let follows = res.body.data
83 83
84 expect(res.body.total).to.equal(2) 84 expect(res.body.total).to.equal(2)
85 expect(follows).to.be.an('array') 85 expect(follows).to.be.an('array')
86 expect(follows.length).to.equal(1) 86 expect(follows.length).to.equal(1)
87 87
88 res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 1, count: 1, sort: 'createdAt' }) 88 res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 1, count: 1, sort: 'createdAt' })
89 follows = follows.concat(res.body.data) 89 follows = follows.concat(res.body.data)
90 90
91 const server2Follow = follows.find(f => f.following.host === 'localhost:' + servers[1].port) 91 const server2Follow = follows.find(f => f.following.host === 'localhost:' + servers[1].port)
@@ -101,7 +101,7 @@ describe('Test follows', function () {
101 const sort = 'createdAt' 101 const sort = 'createdAt'
102 const start = 0 102 const start = 0
103 const count = 1 103 const count = 1
104 const url = servers[ 0 ].url 104 const url = servers[0].url
105 105
106 { 106 {
107 const search = ':' + servers[1].port 107 const search = ':' + servers[1].port
@@ -112,7 +112,7 @@ describe('Test follows', function () {
112 112
113 expect(res.body.total).to.equal(1) 113 expect(res.body.total).to.equal(1)
114 expect(follows.length).to.equal(1) 114 expect(follows.length).to.equal(1)
115 expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[ 1 ].port) 115 expect(follows[0].following.host).to.equal('localhost:' + servers[1].port)
116 } 116 }
117 117
118 { 118 {
@@ -170,9 +170,9 @@ describe('Test follows', function () {
170 170
171 it('Should have 1 followers on server 2 and 3', async function () { 171 it('Should have 1 followers on server 2 and 3', async function () {
172 for (const server of [ servers[1], servers[2] ]) { 172 for (const server of [ servers[1], servers[2] ]) {
173 let res = await getFollowersListPaginationAndSort({ url: server.url, start: 0, count: 1, sort: 'createdAt' }) 173 const res = await getFollowersListPaginationAndSort({ url: server.url, start: 0, count: 1, sort: 'createdAt' })
174 174
175 let follows = res.body.data 175 const follows = res.body.data
176 expect(res.body.total).to.equal(1) 176 expect(res.body.total).to.equal(1)
177 expect(follows).to.be.an('array') 177 expect(follows).to.be.an('array')
178 expect(follows.length).to.equal(1) 178 expect(follows.length).to.equal(1)
@@ -181,7 +181,7 @@ describe('Test follows', function () {
181 }) 181 })
182 182
183 it('Should search/filter followers on server 2', async function () { 183 it('Should search/filter followers on server 2', async function () {
184 const url = servers[ 2 ].url 184 const url = servers[2].url
185 const start = 0 185 const start = 0
186 const count = 5 186 const count = 5
187 const sort = 'createdAt' 187 const sort = 'createdAt'
@@ -195,7 +195,7 @@ describe('Test follows', function () {
195 195
196 expect(res.body.total).to.equal(1) 196 expect(res.body.total).to.equal(1)
197 expect(follows.length).to.equal(1) 197 expect(follows.length).to.equal(1)
198 expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[ 2 ].port) 198 expect(follows[0].following.host).to.equal('localhost:' + servers[2].port)
199 } 199 }
200 200
201 { 201 {
@@ -241,7 +241,7 @@ describe('Test follows', function () {
241 }) 241 })
242 242
243 it('Should have 0 followers on server 1', async function () { 243 it('Should have 0 followers on server 1', async function () {
244 const res = await getFollowersListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' }) 244 const res = await getFollowersListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' })
245 const follows = res.body.data 245 const follows = res.body.data
246 246
247 expect(res.body.total).to.equal(0) 247 expect(res.body.total).to.equal(0)
@@ -271,8 +271,8 @@ describe('Test follows', function () {
271 }) 271 })
272 272
273 it('Should not follow server 3 on server 1 anymore', async function () { 273 it('Should not follow server 3 on server 1 anymore', async function () {
274 const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 2, sort: 'createdAt' }) 274 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 2, sort: 'createdAt' })
275 let follows = res.body.data 275 const follows = res.body.data
276 276
277 expect(res.body.total).to.equal(1) 277 expect(res.body.total).to.equal(1)
278 expect(follows).to.be.an('array') 278 expect(follows).to.be.an('array')
@@ -282,9 +282,9 @@ describe('Test follows', function () {
282 }) 282 })
283 283
284 it('Should not have server 1 as follower on server 3 anymore', async function () { 284 it('Should not have server 1 as follower on server 3 anymore', async function () {
285 const res = await getFollowersListPaginationAndSort({ url: servers[ 2 ].url, start: 0, count: 1, sort: 'createdAt' }) 285 const res = await getFollowersListPaginationAndSort({ url: servers[2].url, start: 0, count: 1, sort: 'createdAt' })
286 286
287 let follows = res.body.data 287 const follows = res.body.data
288 expect(res.body.total).to.equal(0) 288 expect(res.body.total).to.equal(0)
289 expect(follows).to.be.an('array') 289 expect(follows).to.be.an('array')
290 expect(follows.length).to.equal(0) 290 expect(follows.length).to.equal(0)
@@ -336,59 +336,59 @@ describe('Test follows', function () {
336 tags: [ 'tag1', 'tag2', 'tag3' ] 336 tags: [ 'tag1', 'tag2', 'tag3' ]
337 } 337 }
338 338
339 await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-2' }) 339 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-2' })
340 await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-3' }) 340 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-3' })
341 await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, video4Attributes) 341 await uploadVideo(servers[2].url, servers[2].accessToken, video4Attributes)
342 await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-5' }) 342 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-5' })
343 await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-6' }) 343 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-6' })
344 344
345 { 345 {
346 const user = { username: 'captain', password: 'password' } 346 const user = { username: 'captain', password: 'password' }
347 await createUser({ url: servers[ 2 ].url, accessToken: servers[ 2 ].accessToken, username: user.username, password: user.password }) 347 await createUser({ url: servers[2].url, accessToken: servers[2].accessToken, username: user.username, password: user.password })
348 const userAccessToken = await userLogin(servers[ 2 ], user) 348 const userAccessToken = await userLogin(servers[2], user)
349 349
350 const resVideos = await getVideosList(servers[ 2 ].url) 350 const resVideos = await getVideosList(servers[2].url)
351 video4 = resVideos.body.data.find(v => v.name === 'server3-4') 351 video4 = resVideos.body.data.find(v => v.name === 'server3-4')
352 352
353 { 353 {
354 await rateVideo(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, 'like') 354 await rateVideo(servers[2].url, servers[2].accessToken, video4.id, 'like')
355 await rateVideo(servers[ 2 ].url, userAccessToken, video4.id, 'dislike') 355 await rateVideo(servers[2].url, userAccessToken, video4.id, 'dislike')
356 } 356 }
357 357
358 { 358 {
359 { 359 {
360 const text = 'my super first comment' 360 const text = 'my super first comment'
361 const res = await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, text) 361 const res = await addVideoCommentThread(servers[2].url, servers[2].accessToken, video4.id, text)
362 const threadId = res.body.comment.id 362 const threadId = res.body.comment.id
363 363
364 const text1 = 'my super answer to thread 1' 364 const text1 = 'my super answer to thread 1'
365 const childCommentRes = await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text1) 365 const childCommentRes = await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text1)
366 const childCommentId = childCommentRes.body.comment.id 366 const childCommentId = childCommentRes.body.comment.id
367 367
368 const text2 = 'my super answer to answer of thread 1' 368 const text2 = 'my super answer to answer of thread 1'
369 await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId, text2) 369 await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, childCommentId, text2)
370 370
371 const text3 = 'my second answer to thread 1' 371 const text3 = 'my second answer to thread 1'
372 await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text3) 372 await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text3)
373 } 373 }
374 374
375 { 375 {
376 const text = 'will be deleted' 376 const text = 'will be deleted'
377 const res = await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, text) 377 const res = await addVideoCommentThread(servers[2].url, servers[2].accessToken, video4.id, text)
378 const threadId = res.body.comment.id 378 const threadId = res.body.comment.id
379 379
380 const text1 = 'answer to deleted' 380 const text1 = 'answer to deleted'
381 await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text1) 381 await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text1)
382 382
383 const text2 = 'will also be deleted' 383 const text2 = 'will also be deleted'
384 const childCommentRes = await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text2) 384 const childCommentRes = await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text2)
385 const childCommentId = childCommentRes.body.comment.id 385 const childCommentId = childCommentRes.body.comment.id
386 386
387 const text3 = 'my second answer to deleted' 387 const text3 = 'my second answer to deleted'
388 await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId, text3) 388 await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, childCommentId, text3)
389 389
390 await deleteVideoComment(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId) 390 await deleteVideoComment(servers[2].url, servers[2].accessToken, video4.id, threadId)
391 await deleteVideoComment(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId) 391 await deleteVideoComment(servers[2].url, servers[2].accessToken, video4.id, childCommentId)
392 } 392 }
393 } 393 }
394 394
@@ -406,7 +406,7 @@ describe('Test follows', function () {
406 await waitJobs(servers) 406 await waitJobs(servers)
407 407
408 // Server 1 follows server 3 408 // Server 1 follows server 3
409 await follow(servers[ 0 ].url, [ servers[ 2 ].url ], servers[ 0 ].accessToken) 409 await follow(servers[0].url, [ servers[2].url ], servers[0].accessToken)
410 410
411 await waitJobs(servers) 411 await waitJobs(servers)
412 }) 412 })
@@ -424,7 +424,7 @@ describe('Test follows', function () {
424 }) 424 })
425 425
426 it('Should have propagated videos', async function () { 426 it('Should have propagated videos', async function () {
427 const res = await getVideosList(servers[ 0 ].url) 427 const res = await getVideosList(servers[0].url)
428 expect(res.body.total).to.equal(7) 428 expect(res.body.total).to.equal(7)
429 429
430 const video2 = res.body.data.find(v => v.name === 'server3-2') 430 const video2 = res.body.data.find(v => v.name === 'server3-2')
@@ -470,7 +470,7 @@ describe('Test follows', function () {
470 } 470 }
471 ] 471 ]
472 } 472 }
473 await completeVideoCheck(servers[ 0 ].url, video4, checkAttributes) 473 await completeVideoCheck(servers[0].url, video4, checkAttributes)
474 }) 474 })
475 475
476 it('Should have propagated comments', async function () { 476 it('Should have propagated comments', async function () {
@@ -481,34 +481,34 @@ describe('Test follows', function () {
481 expect(res1.body.data).to.have.lengthOf(2) 481 expect(res1.body.data).to.have.lengthOf(2)
482 482
483 { 483 {
484 const comment: VideoComment = res1.body.data[ 0 ] 484 const comment: VideoComment = res1.body.data[0]
485 expect(comment.inReplyToCommentId).to.be.null 485 expect(comment.inReplyToCommentId).to.be.null
486 expect(comment.text).equal('my super first comment') 486 expect(comment.text).equal('my super first comment')
487 expect(comment.videoId).to.equal(video4.id) 487 expect(comment.videoId).to.equal(video4.id)
488 expect(comment.id).to.equal(comment.threadId) 488 expect(comment.id).to.equal(comment.threadId)
489 expect(comment.account.name).to.equal('root') 489 expect(comment.account.name).to.equal('root')
490 expect(comment.account.host).to.equal('localhost:' + servers[ 2 ].port) 490 expect(comment.account.host).to.equal('localhost:' + servers[2].port)
491 expect(comment.totalReplies).to.equal(3) 491 expect(comment.totalReplies).to.equal(3)
492 expect(dateIsValid(comment.createdAt as string)).to.be.true 492 expect(dateIsValid(comment.createdAt as string)).to.be.true
493 expect(dateIsValid(comment.updatedAt as string)).to.be.true 493 expect(dateIsValid(comment.updatedAt as string)).to.be.true
494 494
495 const threadId = comment.threadId 495 const threadId = comment.threadId
496 496
497 const res2 = await getVideoThreadComments(servers[ 0 ].url, video4.id, threadId) 497 const res2 = await getVideoThreadComments(servers[0].url, video4.id, threadId)
498 498
499 const tree: VideoCommentThreadTree = res2.body 499 const tree: VideoCommentThreadTree = res2.body
500 expect(tree.comment.text).equal('my super first comment') 500 expect(tree.comment.text).equal('my super first comment')
501 expect(tree.children).to.have.lengthOf(2) 501 expect(tree.children).to.have.lengthOf(2)
502 502
503 const firstChild = tree.children[ 0 ] 503 const firstChild = tree.children[0]
504 expect(firstChild.comment.text).to.equal('my super answer to thread 1') 504 expect(firstChild.comment.text).to.equal('my super answer to thread 1')
505 expect(firstChild.children).to.have.lengthOf(1) 505 expect(firstChild.children).to.have.lengthOf(1)
506 506
507 const childOfFirstChild = firstChild.children[ 0 ] 507 const childOfFirstChild = firstChild.children[0]
508 expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1') 508 expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
509 expect(childOfFirstChild.children).to.have.lengthOf(0) 509 expect(childOfFirstChild.children).to.have.lengthOf(0)
510 510
511 const secondChild = tree.children[ 1 ] 511 const secondChild = tree.children[1]
512 expect(secondChild.comment.text).to.equal('my second answer to thread 1') 512 expect(secondChild.comment.text).to.equal('my second answer to thread 1')
513 expect(secondChild.children).to.have.lengthOf(0) 513 expect(secondChild.children).to.have.lengthOf(0)
514 } 514 }
@@ -569,7 +569,7 @@ describe('Test follows', function () {
569 569
570 await waitJobs(servers) 570 await waitJobs(servers)
571 571
572 let res = await getVideosList(servers[ 0 ].url) 572 const res = await getVideosList(servers[0].url)
573 expect(res.body.total).to.equal(1) 573 expect(res.body.total).to.equal(1)
574 }) 574 })
575 575
diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts
index 7e36067f1..2cf6e15ad 100644
--- a/server/tests/api/server/handle-down.ts
+++ b/server/tests/api/server/handle-down.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -8,6 +8,7 @@ import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-c
8 8
9import { 9import {
10 cleanupTests, 10 cleanupTests,
11 closeAllSequelize,
11 completeVideoCheck, 12 completeVideoCheck,
12 flushAndRunMultipleServers, 13 flushAndRunMultipleServers,
13 getVideo, 14 getVideo,
@@ -17,11 +18,12 @@ import {
17 reRunServer, 18 reRunServer,
18 ServerInfo, 19 ServerInfo,
19 setAccessTokensToServers, 20 setAccessTokensToServers,
21 setActorFollowScores,
20 unfollow, 22 unfollow,
21 updateVideo, 23 updateVideo,
22 uploadVideo, uploadVideoAndGetId, 24 uploadVideo,
23 wait, 25 uploadVideoAndGetId,
24 setActorFollowScores, closeAllSequelize 26 wait
25} from '../../../../shared/extra-utils' 27} from '../../../../shared/extra-utils'
26import { follow, getFollowersListPaginationAndSort } from '../../../../shared/extra-utils/server/follows' 28import { follow, getFollowersListPaginationAndSort } from '../../../../shared/extra-utils/server/follows'
27import { getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs' 29import { getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs'
@@ -44,7 +46,7 @@ describe('Test handle downs', function () {
44 let missedVideo2: Video 46 let missedVideo2: Video
45 let unlistedVideo: Video 47 let unlistedVideo: Video
46 48
47 let videoIdsServer1: number[] = [] 49 const videoIdsServer1: number[] = []
48 50
49 const videoAttributes = { 51 const videoAttributes = {
50 name: 'my super name for server 1', 52 name: 'my super name for server 1',
@@ -137,7 +139,7 @@ describe('Test handle downs', function () {
137 139
138 // Remove server 2 follower 140 // Remove server 2 follower
139 for (let i = 0; i < 10; i++) { 141 for (let i = 0; i < 10; i++) {
140 await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributes) 142 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
141 } 143 }
142 144
143 await waitJobs(servers[0]) 145 await waitJobs(servers[0])
@@ -145,14 +147,14 @@ describe('Test handle downs', function () {
145 // Kill server 3 147 // Kill server 3
146 killallServers([ servers[2] ]) 148 killallServers([ servers[2] ])
147 149
148 const resLastVideo1 = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributes) 150 const resLastVideo1 = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
149 missedVideo1 = resLastVideo1.body.video 151 missedVideo1 = resLastVideo1.body.video
150 152
151 const resLastVideo2 = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributes) 153 const resLastVideo2 = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
152 missedVideo2 = resLastVideo2.body.video 154 missedVideo2 = resLastVideo2.body.video
153 155
154 // Unlisted video 156 // Unlisted video
155 let resVideo = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, unlistedVideoAttributes) 157 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, unlistedVideoAttributes)
156 unlistedVideo = resVideo.body.video 158 unlistedVideo = resVideo.body.video
157 159
158 // Add comments to video 2 160 // Add comments to video 2
@@ -174,7 +176,7 @@ describe('Test handle downs', function () {
174 await wait(11000) 176 await wait(11000)
175 177
176 // Only server 3 is still a follower of server 1 178 // Only server 3 is still a follower of server 1
177 const res = await getFollowersListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 2, sort: 'createdAt' }) 179 const res = await getFollowersListPaginationAndSort({ url: servers[0].url, start: 0, count: 2, sort: 'createdAt' })
178 expect(res.body.data).to.be.an('array') 180 expect(res.body.data).to.be.an('array')
179 expect(res.body.data).to.have.lengthOf(1) 181 expect(res.body.data).to.have.lengthOf(1)
180 expect(res.body.data[0].follower.host).to.equal('localhost:' + servers[2].port) 182 expect(res.body.data[0].follower.host).to.equal('localhost:' + servers[2].port)
@@ -185,8 +187,8 @@ describe('Test handle downs', function () {
185 187
186 for (const state of states) { 188 for (const state of states) {
187 const res = await getJobsListPaginationAndSort({ 189 const res = await getJobsListPaginationAndSort({
188 url: servers[ 0 ].url, 190 url: servers[0].url,
189 accessToken: servers[ 0 ].accessToken, 191 accessToken: servers[0].accessToken,
190 state: state, 192 state: state,
191 start: 0, 193 start: 0,
192 count: 50, 194 count: 50,
@@ -209,7 +211,7 @@ describe('Test handle downs', function () {
209 211
210 await waitJobs(servers) 212 await waitJobs(servers)
211 213
212 const res = await getFollowersListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 2, sort: 'createdAt' }) 214 const res = await getFollowersListPaginationAndSort({ url: servers[0].url, start: 0, count: 2, sort: 'createdAt' })
213 expect(res.body.data).to.be.an('array') 215 expect(res.body.data).to.be.an('array')
214 expect(res.body.data).to.have.lengthOf(2) 216 expect(res.body.data).to.have.lengthOf(2)
215 }) 217 })
@@ -221,8 +223,8 @@ describe('Test handle downs', function () {
221 expect(res1.body.data).to.be.an('array') 223 expect(res1.body.data).to.be.an('array')
222 expect(res1.body.data).to.have.lengthOf(11) 224 expect(res1.body.data).to.have.lengthOf(11)
223 225
224 await updateVideo(servers[0].url, servers[0].accessToken, missedVideo1.uuid, { }) 226 await updateVideo(servers[0].url, servers[0].accessToken, missedVideo1.uuid, {})
225 await updateVideo(servers[0].url, servers[0].accessToken, unlistedVideo.uuid, { }) 227 await updateVideo(servers[0].url, servers[0].accessToken, unlistedVideo.uuid, {})
226 228
227 await waitJobs(servers) 229 await waitJobs(servers)
228 230
@@ -313,14 +315,14 @@ describe('Test handle downs', function () {
313 this.timeout(120000) 315 this.timeout(120000)
314 316
315 for (let i = 0; i < 10; i++) { 317 for (let i = 0; i < 10; i++) {
316 const uuid = (await uploadVideoAndGetId({ server: servers[ 0 ], videoName: 'video ' + i })).uuid 318 const uuid = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video ' + i })).uuid
317 videoIdsServer1.push(uuid) 319 videoIdsServer1.push(uuid)
318 } 320 }
319 321
320 await waitJobs(servers) 322 await waitJobs(servers)
321 323
322 for (const id of videoIdsServer1) { 324 for (const id of videoIdsServer1) {
323 await getVideo(servers[ 1 ].url, id) 325 await getVideo(servers[1].url, id)
324 } 326 }
325 327
326 await waitJobs(servers) 328 await waitJobs(servers)
diff --git a/server/tests/api/server/jobs.ts b/server/tests/api/server/jobs.ts
index 58d8c8c10..19c8836b5 100644
--- a/server/tests/api/server/jobs.ts
+++ b/server/tests/api/server/jobs.ts
@@ -1,8 +1,8 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { cleanupTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index' 5import { cleanupTests, ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index'
6import { doubleFollow } from '../../../../shared/extra-utils/server/follows' 6import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
7import { getJobsList, getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs' 7import { getJobsList, getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs'
8import { flushAndRunMultipleServers } from '../../../../shared/extra-utils/server/servers' 8import { flushAndRunMultipleServers } from '../../../../shared/extra-utils/server/servers'
@@ -44,8 +44,8 @@ describe('Test jobs', function () {
44 it('Should list jobs with sort, pagination and job type', async function () { 44 it('Should list jobs with sort, pagination and job type', async function () {
45 { 45 {
46 const res = await getJobsListPaginationAndSort({ 46 const res = await getJobsListPaginationAndSort({
47 url: servers[ 1 ].url, 47 url: servers[1].url,
48 accessToken: servers[ 1 ].accessToken, 48 accessToken: servers[1].accessToken,
49 state: 'completed', 49 state: 'completed',
50 start: 1, 50 start: 1,
51 count: 2, 51 count: 2,
@@ -54,9 +54,9 @@ describe('Test jobs', function () {
54 expect(res.body.total).to.be.above(2) 54 expect(res.body.total).to.be.above(2)
55 expect(res.body.data).to.have.lengthOf(2) 55 expect(res.body.data).to.have.lengthOf(2)
56 56
57 let job: Job = res.body.data[ 0 ] 57 let job: Job = res.body.data[0]
58 // Skip repeat jobs 58 // Skip repeat jobs
59 if (job.type === 'videos-views') job = res.body.data[ 1 ] 59 if (job.type === 'videos-views') job = res.body.data[1]
60 60
61 expect(job.state).to.equal('completed') 61 expect(job.state).to.equal('completed')
62 expect(job.type.startsWith('activitypub-')).to.be.true 62 expect(job.type.startsWith('activitypub-')).to.be.true
@@ -67,8 +67,8 @@ describe('Test jobs', function () {
67 67
68 { 68 {
69 const res = await getJobsListPaginationAndSort({ 69 const res = await getJobsListPaginationAndSort({
70 url: servers[ 1 ].url, 70 url: servers[1].url,
71 accessToken: servers[ 1 ].accessToken, 71 accessToken: servers[1].accessToken,
72 state: 'completed', 72 state: 'completed',
73 start: 0, 73 start: 0,
74 count: 100, 74 count: 100,
diff --git a/server/tests/api/server/logs.ts b/server/tests/api/server/logs.ts
index d3c877408..b8714c7a1 100644
--- a/server/tests/api/server/logs.ts
+++ b/server/tests/api/server/logs.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/server/no-client.ts b/server/tests/api/server/no-client.ts
index 86edeb289..d0450aba0 100644
--- a/server/tests/api/server/no-client.ts
+++ b/server/tests/api/server/no-client.ts
@@ -9,7 +9,7 @@ describe('Start and stop server without web client routes', function () {
9 before(async function () { 9 before(async function () {
10 this.timeout(30000) 10 this.timeout(30000)
11 11
12 server = await flushAndRunServer(1, {}, ['--no-client']) 12 server = await flushAndRunServer(1, {}, [ '--no-client' ])
13 }) 13 })
14 14
15 it('Should fail getting the client', function () { 15 it('Should fail getting the client', function () {
diff --git a/server/tests/api/server/plugins.ts b/server/tests/api/server/plugins.ts
index b8a8a2fee..452d05012 100644
--- a/server/tests/api/server/plugins.ts
+++ b/server/tests/api/server/plugins.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
@@ -6,19 +6,28 @@ import {
6 cleanupTests, 6 cleanupTests,
7 closeAllSequelize, 7 closeAllSequelize,
8 flushAndRunServer, 8 flushAndRunServer,
9 getConfig, getMyUserInformation, getPluginPackageJSON, 9 getConfig,
10 getMyUserInformation,
10 getPlugin, 11 getPlugin,
12 getPluginPackageJSON,
11 getPluginRegisteredSettings, 13 getPluginRegisteredSettings,
12 getPluginsCSS, 14 getPluginsCSS,
13 installPlugin, killallServers, 15 getPublicSettings,
16 installPlugin,
17 killallServers,
14 listAvailablePlugins, 18 listAvailablePlugins,
15 listPlugins, reRunServer, 19 listPlugins,
20 reRunServer,
16 ServerInfo, 21 ServerInfo,
17 setAccessTokensToServers, 22 setAccessTokensToServers,
18 setPluginVersion, uninstallPlugin, 23 setPluginVersion,
19 updateCustomSubConfig, updateMyUser, updatePluginPackageJSON, updatePlugin, 24 uninstallPlugin,
25 updateCustomSubConfig,
26 updateMyUser,
27 updatePlugin,
28 updatePluginPackageJSON,
20 updatePluginSettings, 29 updatePluginSettings,
21 wait, getPublicSettings 30 wait
22} from '../../../../shared/extra-utils' 31} from '../../../../shared/extra-utils'
23import { PluginType } from '../../../../shared/models/plugins/plugin.type' 32import { PluginType } from '../../../../shared/models/plugins/plugin.type'
24import { PeerTubePluginIndex } from '../../../../shared/models/plugins/peertube-plugin-index.model' 33import { PeerTubePluginIndex } from '../../../../shared/models/plugins/peertube-plugin-index.model'
@@ -88,7 +97,7 @@ describe('Test plugins', function () {
88 expect(res2.body.total).to.be.at.least(2) 97 expect(res2.body.total).to.be.at.least(2)
89 expect(data2).to.have.lengthOf(2) 98 expect(data2).to.have.lengthOf(2)
90 99
91 expect(data1[0].npmName).to.not.equal(data2[ 0 ].npmName) 100 expect(data1[0].npmName).to.not.equal(data2[0].npmName)
92 } 101 }
93 102
94 { 103 {
diff --git a/server/tests/api/server/reverse-proxy.ts b/server/tests/api/server/reverse-proxy.ts
index b6b33a884..d0d79c4f6 100644
--- a/server/tests/api/server/reverse-proxy.ts
+++ b/server/tests/api/server/reverse-proxy.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
diff --git a/server/tests/api/server/stats.ts b/server/tests/api/server/stats.ts
index a01cd4b38..c207bb5f0 100644
--- a/server/tests/api/server/stats.ts
+++ b/server/tests/api/server/stats.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -9,13 +9,12 @@ import {
9 doubleFollow, 9 doubleFollow,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
11 follow, 11 follow,
12 killallServers,
13 ServerInfo, 12 ServerInfo,
14 uploadVideo, 13 uploadVideo,
15 viewVideo, 14 viewVideo,
16 wait 15 wait
17} from '../../../../shared/extra-utils' 16} from '../../../../shared/extra-utils'
18import { flushTests, setAccessTokensToServers } from '../../../../shared/extra-utils/index' 17import { setAccessTokensToServers } from '../../../../shared/extra-utils/index'
19import { getStats } from '../../../../shared/extra-utils/server/stats' 18import { getStats } from '../../../../shared/extra-utils/server/stats'
20import { addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' 19import { addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments'
21import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 20import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
@@ -36,7 +35,7 @@ describe('Test stats (excluding redundancy)', function () {
36 username: 'user1', 35 username: 'user1',
37 password: 'super_password' 36 password: 'super_password'
38 } 37 }
39 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) 38 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
40 39
41 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' }) 40 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' })
42 const videoUUID = resVideo.body.video.uuid 41 const videoUUID = resVideo.body.video.uuid
diff --git a/server/tests/api/server/tracker.ts b/server/tests/api/server/tracker.ts
index 9d7eec8ca..9d3a274d4 100644
--- a/server/tests/api/server/tracker.ts
+++ b/server/tests/api/server/tracker.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await,@typescript-eslint/no-floating-promises */
2 2
3import * as magnetUtil from 'magnet-uri' 3import * as magnetUtil from 'magnet-uri'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/users/blocklist.ts b/server/tests/api/users/blocklist.ts
index 05e58017a..21b9ae4f8 100644
--- a/server/tests/api/users/blocklist.ts
+++ b/server/tests/api/users/blocklist.ts
@@ -1,21 +1,20 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { AccountBlock, ServerBlock, UserNotificationType, Video } from '../../../../shared/index' 5import { AccountBlock, ServerBlock, Video } from '../../../../shared/index'
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 createUser, deleteVideoComment, 8 createUser,
9 deleteVideoComment,
9 doubleFollow, 10 doubleFollow,
10 flushAndRunMultipleServers, 11 flushAndRunMultipleServers,
11 flushTests,
12 killallServers,
13 ServerInfo, 12 ServerInfo,
14 uploadVideo, 13 uploadVideo,
15 userLogin 14 userLogin
16} from '../../../../shared/extra-utils/index' 15} from '../../../../shared/extra-utils/index'
17import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' 16import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
18import { getVideosListWithToken, getVideosList } from '../../../../shared/extra-utils/videos/videos' 17import { getVideosList, getVideosListWithToken } from '../../../../shared/extra-utils/videos/videos'
19import { 18import {
20 addVideoCommentReply, 19 addVideoCommentReply,
21 addVideoCommentThread, 20 addVideoCommentThread,
@@ -79,7 +78,7 @@ async function checkCommentNotification (
79 const resComment = await addVideoCommentThread(comment.server.url, comment.token, comment.videoUUID, comment.text) 78 const resComment = await addVideoCommentThread(comment.server.url, comment.token, comment.videoUUID, comment.text)
80 const threadId = resComment.body.comment.id 79 const threadId = resComment.body.comment.id
81 80
82 await waitJobs([ mainServer, comment.server]) 81 await waitJobs([ mainServer, comment.server ])
83 82
84 const res = await getUserNotifications(mainServer.url, mainServer.accessToken, 0, 30) 83 const res = await getUserNotifications(mainServer.url, mainServer.accessToken, 0, 30)
85 const commentNotifications = res.body.data 84 const commentNotifications = res.body.data
@@ -90,7 +89,7 @@ async function checkCommentNotification (
90 89
91 await deleteVideoComment(comment.server.url, comment.token, comment.videoUUID, threadId) 90 await deleteVideoComment(comment.server.url, comment.token, comment.videoUUID, threadId)
92 91
93 await waitJobs([ mainServer, comment.server]) 92 await waitJobs([ mainServer, comment.server ])
94} 93}
95 94
96describe('Test blocklist', function () { 95describe('Test blocklist', function () {
@@ -109,7 +108,7 @@ describe('Test blocklist', function () {
109 108
110 { 109 {
111 const user = { username: 'user1', password: 'password' } 110 const user = { username: 'user1', password: 'password' }
112 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) 111 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
113 112
114 userToken1 = await userLogin(servers[0], user) 113 userToken1 = await userLogin(servers[0], user)
115 await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' }) 114 await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' })
@@ -117,14 +116,14 @@ describe('Test blocklist', function () {
117 116
118 { 117 {
119 const user = { username: 'moderator', password: 'password' } 118 const user = { username: 'moderator', password: 'password' }
120 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) 119 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
121 120
122 userModeratorToken = await userLogin(servers[0], user) 121 userModeratorToken = await userLogin(servers[0], user)
123 } 122 }
124 123
125 { 124 {
126 const user = { username: 'user2', password: 'password' } 125 const user = { username: 'user2', password: 'password' }
127 await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password }) 126 await createUser({ url: servers[1].url, accessToken: servers[1].accessToken, username: user.username, password: user.password })
128 127
129 userToken2 = await userLogin(servers[1], user) 128 userToken2 = await userLogin(servers[1], user)
130 await uploadVideo(servers[1].url, userToken2, { name: 'video user 2' }) 129 await uploadVideo(servers[1].url, userToken2, { name: 'video user 2' })
@@ -143,14 +142,14 @@ describe('Test blocklist', function () {
143 await doubleFollow(servers[0], servers[1]) 142 await doubleFollow(servers[0], servers[1])
144 143
145 { 144 {
146 const resComment = await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, 'comment root 1') 145 const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID1, 'comment root 1')
147 const resReply = await addVideoCommentReply(servers[ 0 ].url, userToken1, videoUUID1, resComment.body.comment.id, 'comment user 1') 146 const resReply = await addVideoCommentReply(servers[0].url, userToken1, videoUUID1, resComment.body.comment.id, 'comment user 1')
148 await addVideoCommentReply(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, resReply.body.comment.id, 'comment root 1') 147 await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID1, resReply.body.comment.id, 'comment root 1')
149 } 148 }
150 149
151 { 150 {
152 const resComment = await addVideoCommentThread(servers[ 0 ].url, userToken1, videoUUID1, 'comment user 1') 151 const resComment = await addVideoCommentThread(servers[0].url, userToken1, videoUUID1, 'comment user 1')
153 await addVideoCommentReply(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, resComment.body.comment.id, 'comment root 1') 152 await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID1, resComment.body.comment.id, 'comment root 1')
154 } 153 }
155 154
156 await waitJobs(servers) 155 await waitJobs(servers)
@@ -160,19 +159,19 @@ describe('Test blocklist', function () {
160 159
161 describe('When managing account blocklist', function () { 160 describe('When managing account blocklist', function () {
162 it('Should list all videos', function () { 161 it('Should list all videos', function () {
163 return checkAllVideos(servers[ 0 ].url, servers[ 0 ].accessToken) 162 return checkAllVideos(servers[0].url, servers[0].accessToken)
164 }) 163 })
165 164
166 it('Should list the comments', function () { 165 it('Should list the comments', function () {
167 return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 166 return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
168 }) 167 })
169 168
170 it('Should block a remote account', async function () { 169 it('Should block a remote account', async function () {
171 await addAccountToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port) 170 await addAccountToAccountBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port)
172 }) 171 })
173 172
174 it('Should hide its videos', async function () { 173 it('Should hide its videos', async function () {
175 const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) 174 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
176 175
177 const videos: Video[] = res.body.data 176 const videos: Video[] = res.body.data
178 expect(videos).to.have.lengthOf(3) 177 expect(videos).to.have.lengthOf(3)
@@ -182,11 +181,11 @@ describe('Test blocklist', function () {
182 }) 181 })
183 182
184 it('Should block a local account', async function () { 183 it('Should block a local account', async function () {
185 await addAccountToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') 184 await addAccountToAccountBlocklist(servers[0].url, servers[0].accessToken, 'user1')
186 }) 185 })
187 186
188 it('Should hide its videos', async function () { 187 it('Should hide its videos', async function () {
189 const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) 188 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
190 189
191 const videos: Video[] = res.body.data 190 const videos: Video[] = res.body.data
192 expect(videos).to.have.lengthOf(2) 191 expect(videos).to.have.lengthOf(2)
@@ -196,17 +195,17 @@ describe('Test blocklist', function () {
196 }) 195 })
197 196
198 it('Should hide its comments', async function () { 197 it('Should hide its comments', async function () {
199 const resThreads = await getVideoCommentThreads(servers[ 0 ].url, videoUUID1, 0, 5, '-createdAt', servers[ 0 ].accessToken) 198 const resThreads = await getVideoCommentThreads(servers[0].url, videoUUID1, 0, 5, '-createdAt', servers[0].accessToken)
200 199
201 const threads: VideoComment[] = resThreads.body.data 200 const threads: VideoComment[] = resThreads.body.data
202 expect(threads).to.have.lengthOf(1) 201 expect(threads).to.have.lengthOf(1)
203 expect(threads[ 0 ].totalReplies).to.equal(0) 202 expect(threads[0].totalReplies).to.equal(0)
204 203
205 const t = threads.find(t => t.text === 'comment user 1') 204 const t = threads.find(t => t.text === 'comment user 1')
206 expect(t).to.be.undefined 205 expect(t).to.be.undefined
207 206
208 for (const thread of threads) { 207 for (const thread of threads) {
209 const res = await getVideoThreadComments(servers[ 0 ].url, videoUUID1, thread.id, servers[ 0 ].accessToken) 208 const res = await getVideoThreadComments(servers[0].url, videoUUID1, thread.id, servers[0].accessToken)
210 209
211 const tree: VideoCommentThreadTree = res.body 210 const tree: VideoCommentThreadTree = res.body
212 expect(tree.children).to.have.lengthOf(0) 211 expect(tree.children).to.have.lengthOf(0)
@@ -217,37 +216,37 @@ describe('Test blocklist', function () {
217 this.timeout(20000) 216 this.timeout(20000)
218 217
219 { 218 {
220 const comment = { server: servers[ 0 ], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' } 219 const comment = { server: servers[0], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' }
221 await checkCommentNotification(servers[ 0 ], comment, 'absence') 220 await checkCommentNotification(servers[0], comment, 'absence')
222 } 221 }
223 222
224 { 223 {
225 const comment = { 224 const comment = {
226 server: servers[ 0 ], 225 server: servers[0],
227 token: userToken1, 226 token: userToken1,
228 videoUUID: videoUUID2, 227 videoUUID: videoUUID2,
229 text: 'hello @root@localhost:' + servers[ 0 ].port 228 text: 'hello @root@localhost:' + servers[0].port
230 } 229 }
231 await checkCommentNotification(servers[ 0 ], comment, 'absence') 230 await checkCommentNotification(servers[0], comment, 'absence')
232 } 231 }
233 }) 232 })
234 233
235 it('Should list all the videos with another user', async function () { 234 it('Should list all the videos with another user', async function () {
236 return checkAllVideos(servers[ 0 ].url, userToken1) 235 return checkAllVideos(servers[0].url, userToken1)
237 }) 236 })
238 237
239 it('Should list all the comments with another user', async function () { 238 it('Should list all the comments with another user', async function () {
240 return checkAllComments(servers[ 0 ].url, userToken1, videoUUID1) 239 return checkAllComments(servers[0].url, userToken1, videoUUID1)
241 }) 240 })
242 241
243 it('Should list blocked accounts', async function () { 242 it('Should list blocked accounts', async function () {
244 { 243 {
245 const res = await getAccountBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') 244 const res = await getAccountBlocklistByAccount(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt')
246 const blocks: AccountBlock[] = res.body.data 245 const blocks: AccountBlock[] = res.body.data
247 246
248 expect(res.body.total).to.equal(2) 247 expect(res.body.total).to.equal(2)
249 248
250 const block = blocks[ 0 ] 249 const block = blocks[0]
251 expect(block.byAccount.displayName).to.equal('root') 250 expect(block.byAccount.displayName).to.equal('root')
252 expect(block.byAccount.name).to.equal('root') 251 expect(block.byAccount.name).to.equal('root')
253 expect(block.blockedAccount.displayName).to.equal('user2') 252 expect(block.blockedAccount.displayName).to.equal('user2')
@@ -256,12 +255,12 @@ describe('Test blocklist', function () {
256 } 255 }
257 256
258 { 257 {
259 const res = await getAccountBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 1, 2, 'createdAt') 258 const res = await getAccountBlocklistByAccount(servers[0].url, servers[0].accessToken, 1, 2, 'createdAt')
260 const blocks: AccountBlock[] = res.body.data 259 const blocks: AccountBlock[] = res.body.data
261 260
262 expect(res.body.total).to.equal(2) 261 expect(res.body.total).to.equal(2)
263 262
264 const block = blocks[ 0 ] 263 const block = blocks[0]
265 expect(block.byAccount.displayName).to.equal('root') 264 expect(block.byAccount.displayName).to.equal('root')
266 expect(block.byAccount.name).to.equal('root') 265 expect(block.byAccount.name).to.equal('root')
267 expect(block.blockedAccount.displayName).to.equal('user1') 266 expect(block.blockedAccount.displayName).to.equal('user1')
@@ -271,11 +270,11 @@ describe('Test blocklist', function () {
271 }) 270 })
272 271
273 it('Should unblock the remote account', async function () { 272 it('Should unblock the remote account', async function () {
274 await removeAccountFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port) 273 await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port)
275 }) 274 })
276 275
277 it('Should display its videos', async function () { 276 it('Should display its videos', async function () {
278 const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) 277 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
279 278
280 const videos: Video[] = res.body.data 279 const videos: Video[] = res.body.data
281 expect(videos).to.have.lengthOf(3) 280 expect(videos).to.have.lengthOf(3)
@@ -285,48 +284,48 @@ describe('Test blocklist', function () {
285 }) 284 })
286 285
287 it('Should unblock the local account', async function () { 286 it('Should unblock the local account', async function () {
288 await removeAccountFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') 287 await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user1')
289 }) 288 })
290 289
291 it('Should display its comments', function () { 290 it('Should display its comments', function () {
292 return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 291 return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
293 }) 292 })
294 293
295 it('Should have a notification from a non blocked account', async function () { 294 it('Should have a notification from a non blocked account', async function () {
296 this.timeout(20000) 295 this.timeout(20000)
297 296
298 { 297 {
299 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } 298 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
300 await checkCommentNotification(servers[ 0 ], comment, 'presence') 299 await checkCommentNotification(servers[0], comment, 'presence')
301 } 300 }
302 301
303 { 302 {
304 const comment = { 303 const comment = {
305 server: servers[ 0 ], 304 server: servers[0],
306 token: userToken1, 305 token: userToken1,
307 videoUUID: videoUUID2, 306 videoUUID: videoUUID2,
308 text: 'hello @root@localhost:' + servers[ 0 ].port 307 text: 'hello @root@localhost:' + servers[0].port
309 } 308 }
310 await checkCommentNotification(servers[ 0 ], comment, 'presence') 309 await checkCommentNotification(servers[0], comment, 'presence')
311 } 310 }
312 }) 311 })
313 }) 312 })
314 313
315 describe('When managing server blocklist', function () { 314 describe('When managing server blocklist', function () {
316 it('Should list all videos', function () { 315 it('Should list all videos', function () {
317 return checkAllVideos(servers[ 0 ].url, servers[ 0 ].accessToken) 316 return checkAllVideos(servers[0].url, servers[0].accessToken)
318 }) 317 })
319 318
320 it('Should list the comments', function () { 319 it('Should list the comments', function () {
321 return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 320 return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
322 }) 321 })
323 322
324 it('Should block a remote server', async function () { 323 it('Should block a remote server', async function () {
325 await addServerToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) 324 await addServerToAccountBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
326 }) 325 })
327 326
328 it('Should hide its videos', async function () { 327 it('Should hide its videos', async function () {
329 const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) 328 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
330 329
331 const videos: Video[] = res.body.data 330 const videos: Video[] = res.body.data
332 expect(videos).to.have.lengthOf(2) 331 expect(videos).to.have.lengthOf(2)
@@ -339,81 +338,81 @@ describe('Test blocklist', function () {
339 }) 338 })
340 339
341 it('Should list all the videos with another user', async function () { 340 it('Should list all the videos with another user', async function () {
342 return checkAllVideos(servers[ 0 ].url, userToken1) 341 return checkAllVideos(servers[0].url, userToken1)
343 }) 342 })
344 343
345 it('Should hide its comments', async function () { 344 it('Should hide its comments', async function () {
346 this.timeout(10000) 345 this.timeout(10000)
347 346
348 const resThreads = await addVideoCommentThread(servers[ 1 ].url, userToken2, videoUUID1, 'hidden comment 2') 347 const resThreads = await addVideoCommentThread(servers[1].url, userToken2, videoUUID1, 'hidden comment 2')
349 const threadId = resThreads.body.comment.id 348 const threadId = resThreads.body.comment.id
350 349
351 await waitJobs(servers) 350 await waitJobs(servers)
352 351
353 await checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 352 await checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
354 353
355 await deleteVideoComment(servers[ 1 ].url, userToken2, videoUUID1, threadId) 354 await deleteVideoComment(servers[1].url, userToken2, videoUUID1, threadId)
356 }) 355 })
357 356
358 it('Should not have notifications from blocked server', async function () { 357 it('Should not have notifications from blocked server', async function () {
359 this.timeout(20000) 358 this.timeout(20000)
360 359
361 { 360 {
362 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' } 361 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' }
363 await checkCommentNotification(servers[ 0 ], comment, 'absence') 362 await checkCommentNotification(servers[0], comment, 'absence')
364 } 363 }
365 364
366 { 365 {
367 const comment = { 366 const comment = {
368 server: servers[ 1 ], 367 server: servers[1],
369 token: userToken2, 368 token: userToken2,
370 videoUUID: videoUUID1, 369 videoUUID: videoUUID1,
371 text: 'hello @root@localhost:' + servers[ 0 ].port 370 text: 'hello @root@localhost:' + servers[0].port
372 } 371 }
373 await checkCommentNotification(servers[ 0 ], comment, 'absence') 372 await checkCommentNotification(servers[0], comment, 'absence')
374 } 373 }
375 }) 374 })
376 375
377 it('Should list blocked servers', async function () { 376 it('Should list blocked servers', async function () {
378 const res = await getServerBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') 377 const res = await getServerBlocklistByAccount(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt')
379 const blocks: ServerBlock[] = res.body.data 378 const blocks: ServerBlock[] = res.body.data
380 379
381 expect(res.body.total).to.equal(1) 380 expect(res.body.total).to.equal(1)
382 381
383 const block = blocks[ 0 ] 382 const block = blocks[0]
384 expect(block.byAccount.displayName).to.equal('root') 383 expect(block.byAccount.displayName).to.equal('root')
385 expect(block.byAccount.name).to.equal('root') 384 expect(block.byAccount.name).to.equal('root')
386 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) 385 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port)
387 }) 386 })
388 387
389 it('Should unblock the remote server', async function () { 388 it('Should unblock the remote server', async function () {
390 await removeServerFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) 389 await removeServerFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
391 }) 390 })
392 391
393 it('Should display its videos', function () { 392 it('Should display its videos', function () {
394 return checkAllVideos(servers[ 0 ].url, servers[ 0 ].accessToken) 393 return checkAllVideos(servers[0].url, servers[0].accessToken)
395 }) 394 })
396 395
397 it('Should display its comments', function () { 396 it('Should display its comments', function () {
398 return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 397 return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
399 }) 398 })
400 399
401 it('Should have notification from unblocked server', async function () { 400 it('Should have notification from unblocked server', async function () {
402 this.timeout(20000) 401 this.timeout(20000)
403 402
404 { 403 {
405 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } 404 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
406 await checkCommentNotification(servers[ 0 ], comment, 'presence') 405 await checkCommentNotification(servers[0], comment, 'presence')
407 } 406 }
408 407
409 { 408 {
410 const comment = { 409 const comment = {
411 server: servers[ 1 ], 410 server: servers[1],
412 token: userToken2, 411 token: userToken2,
413 videoUUID: videoUUID1, 412 videoUUID: videoUUID1,
414 text: 'hello @root@localhost:' + servers[ 0 ].port 413 text: 'hello @root@localhost:' + servers[0].port
415 } 414 }
416 await checkCommentNotification(servers[ 0 ], comment, 'presence') 415 await checkCommentNotification(servers[0], comment, 'presence')
417 } 416 }
418 }) 417 })
419 }) 418 })
@@ -423,24 +422,24 @@ describe('Test blocklist', function () {
423 422
424 describe('When managing account blocklist', function () { 423 describe('When managing account blocklist', function () {
425 it('Should list all videos', async function () { 424 it('Should list all videos', async function () {
426 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 425 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
427 await checkAllVideos(servers[ 0 ].url, token) 426 await checkAllVideos(servers[0].url, token)
428 } 427 }
429 }) 428 })
430 429
431 it('Should list the comments', async function () { 430 it('Should list the comments', async function () {
432 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 431 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
433 await checkAllComments(servers[ 0 ].url, token, videoUUID1) 432 await checkAllComments(servers[0].url, token, videoUUID1)
434 } 433 }
435 }) 434 })
436 435
437 it('Should block a remote account', async function () { 436 it('Should block a remote account', async function () {
438 await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port) 437 await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port)
439 }) 438 })
440 439
441 it('Should hide its videos', async function () { 440 it('Should hide its videos', async function () {
442 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 441 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
443 const res = await getVideosListWithToken(servers[ 0 ].url, token) 442 const res = await getVideosListWithToken(servers[0].url, token)
444 443
445 const videos: Video[] = res.body.data 444 const videos: Video[] = res.body.data
446 expect(videos).to.have.lengthOf(3) 445 expect(videos).to.have.lengthOf(3)
@@ -451,12 +450,12 @@ describe('Test blocklist', function () {
451 }) 450 })
452 451
453 it('Should block a local account', async function () { 452 it('Should block a local account', async function () {
454 await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') 453 await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'user1')
455 }) 454 })
456 455
457 it('Should hide its videos', async function () { 456 it('Should hide its videos', async function () {
458 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 457 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
459 const res = await getVideosListWithToken(servers[ 0 ].url, token) 458 const res = await getVideosListWithToken(servers[0].url, token)
460 459
461 const videos: Video[] = res.body.data 460 const videos: Video[] = res.body.data
462 expect(videos).to.have.lengthOf(2) 461 expect(videos).to.have.lengthOf(2)
@@ -467,18 +466,18 @@ describe('Test blocklist', function () {
467 }) 466 })
468 467
469 it('Should hide its comments', async function () { 468 it('Should hide its comments', async function () {
470 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 469 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
471 const resThreads = await getVideoCommentThreads(servers[ 0 ].url, videoUUID1, 0, 5, '-createdAt', token) 470 const resThreads = await getVideoCommentThreads(servers[0].url, videoUUID1, 0, 5, '-createdAt', token)
472 471
473 const threads: VideoComment[] = resThreads.body.data 472 const threads: VideoComment[] = resThreads.body.data
474 expect(threads).to.have.lengthOf(1) 473 expect(threads).to.have.lengthOf(1)
475 expect(threads[ 0 ].totalReplies).to.equal(0) 474 expect(threads[0].totalReplies).to.equal(0)
476 475
477 const t = threads.find(t => t.text === 'comment user 1') 476 const t = threads.find(t => t.text === 'comment user 1')
478 expect(t).to.be.undefined 477 expect(t).to.be.undefined
479 478
480 for (const thread of threads) { 479 for (const thread of threads) {
481 const res = await getVideoThreadComments(servers[ 0 ].url, videoUUID1, thread.id, token) 480 const res = await getVideoThreadComments(servers[0].url, videoUUID1, thread.id, token)
482 481
483 const tree: VideoCommentThreadTree = res.body 482 const tree: VideoCommentThreadTree = res.body
484 expect(tree.children).to.have.lengthOf(0) 483 expect(tree.children).to.have.lengthOf(0)
@@ -490,29 +489,29 @@ describe('Test blocklist', function () {
490 this.timeout(20000) 489 this.timeout(20000)
491 490
492 { 491 {
493 const comment = { server: servers[ 0 ], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' } 492 const comment = { server: servers[0], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' }
494 await checkCommentNotification(servers[ 0 ], comment, 'absence') 493 await checkCommentNotification(servers[0], comment, 'absence')
495 } 494 }
496 495
497 { 496 {
498 const comment = { 497 const comment = {
499 server: servers[ 1 ], 498 server: servers[1],
500 token: userToken2, 499 token: userToken2,
501 videoUUID: videoUUID1, 500 videoUUID: videoUUID1,
502 text: 'hello @root@localhost:' + servers[ 0 ].port 501 text: 'hello @root@localhost:' + servers[0].port
503 } 502 }
504 await checkCommentNotification(servers[ 0 ], comment, 'absence') 503 await checkCommentNotification(servers[0], comment, 'absence')
505 } 504 }
506 }) 505 })
507 506
508 it('Should list blocked accounts', async function () { 507 it('Should list blocked accounts', async function () {
509 { 508 {
510 const res = await getAccountBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') 509 const res = await getAccountBlocklistByServer(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt')
511 const blocks: AccountBlock[] = res.body.data 510 const blocks: AccountBlock[] = res.body.data
512 511
513 expect(res.body.total).to.equal(2) 512 expect(res.body.total).to.equal(2)
514 513
515 const block = blocks[ 0 ] 514 const block = blocks[0]
516 expect(block.byAccount.displayName).to.equal('peertube') 515 expect(block.byAccount.displayName).to.equal('peertube')
517 expect(block.byAccount.name).to.equal('peertube') 516 expect(block.byAccount.name).to.equal('peertube')
518 expect(block.blockedAccount.displayName).to.equal('user2') 517 expect(block.blockedAccount.displayName).to.equal('user2')
@@ -521,12 +520,12 @@ describe('Test blocklist', function () {
521 } 520 }
522 521
523 { 522 {
524 const res = await getAccountBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 1, 2, 'createdAt') 523 const res = await getAccountBlocklistByServer(servers[0].url, servers[0].accessToken, 1, 2, 'createdAt')
525 const blocks: AccountBlock[] = res.body.data 524 const blocks: AccountBlock[] = res.body.data
526 525
527 expect(res.body.total).to.equal(2) 526 expect(res.body.total).to.equal(2)
528 527
529 const block = blocks[ 0 ] 528 const block = blocks[0]
530 expect(block.byAccount.displayName).to.equal('peertube') 529 expect(block.byAccount.displayName).to.equal('peertube')
531 expect(block.byAccount.name).to.equal('peertube') 530 expect(block.byAccount.name).to.equal('peertube')
532 expect(block.blockedAccount.displayName).to.equal('user1') 531 expect(block.blockedAccount.displayName).to.equal('user1')
@@ -536,12 +535,12 @@ describe('Test blocklist', function () {
536 }) 535 })
537 536
538 it('Should unblock the remote account', async function () { 537 it('Should unblock the remote account', async function () {
539 await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port) 538 await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port)
540 }) 539 })
541 540
542 it('Should display its videos', async function () { 541 it('Should display its videos', async function () {
543 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 542 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
544 const res = await getVideosListWithToken(servers[ 0 ].url, token) 543 const res = await getVideosListWithToken(servers[0].url, token)
545 544
546 const videos: Video[] = res.body.data 545 const videos: Video[] = res.body.data
547 expect(videos).to.have.lengthOf(3) 546 expect(videos).to.have.lengthOf(3)
@@ -552,12 +551,12 @@ describe('Test blocklist', function () {
552 }) 551 })
553 552
554 it('Should unblock the local account', async function () { 553 it('Should unblock the local account', async function () {
555 await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') 554 await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, 'user1')
556 }) 555 })
557 556
558 it('Should display its comments', async function () { 557 it('Should display its comments', async function () {
559 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 558 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
560 await checkAllComments(servers[ 0 ].url, token, videoUUID1) 559 await checkAllComments(servers[0].url, token, videoUUID1)
561 } 560 }
562 }) 561 })
563 562
@@ -565,43 +564,43 @@ describe('Test blocklist', function () {
565 this.timeout(20000) 564 this.timeout(20000)
566 565
567 { 566 {
568 const comment = { server: servers[ 0 ], token: userToken1, videoUUID: videoUUID1, text: 'displayed comment' } 567 const comment = { server: servers[0], token: userToken1, videoUUID: videoUUID1, text: 'displayed comment' }
569 await checkCommentNotification(servers[ 0 ], comment, 'presence') 568 await checkCommentNotification(servers[0], comment, 'presence')
570 } 569 }
571 570
572 { 571 {
573 const comment = { 572 const comment = {
574 server: servers[ 1 ], 573 server: servers[1],
575 token: userToken2, 574 token: userToken2,
576 videoUUID: videoUUID1, 575 videoUUID: videoUUID1,
577 text: 'hello @root@localhost:' + servers[ 0 ].port 576 text: 'hello @root@localhost:' + servers[0].port
578 } 577 }
579 await checkCommentNotification(servers[ 0 ], comment, 'presence') 578 await checkCommentNotification(servers[0], comment, 'presence')
580 } 579 }
581 }) 580 })
582 }) 581 })
583 582
584 describe('When managing server blocklist', function () { 583 describe('When managing server blocklist', function () {
585 it('Should list all videos', async function () { 584 it('Should list all videos', async function () {
586 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 585 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
587 await checkAllVideos(servers[ 0 ].url, token) 586 await checkAllVideos(servers[0].url, token)
588 } 587 }
589 }) 588 })
590 589
591 it('Should list the comments', async function () { 590 it('Should list the comments', async function () {
592 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 591 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
593 await checkAllComments(servers[ 0 ].url, token, videoUUID1) 592 await checkAllComments(servers[0].url, token, videoUUID1)
594 } 593 }
595 }) 594 })
596 595
597 it('Should block a remote server', async function () { 596 it('Should block a remote server', async function () {
598 await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) 597 await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
599 }) 598 })
600 599
601 it('Should hide its videos', async function () { 600 it('Should hide its videos', async function () {
602 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 601 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
603 const res1 = await getVideosList(servers[ 0 ].url) 602 const res1 = await getVideosList(servers[0].url)
604 const res2 = await getVideosListWithToken(servers[ 0 ].url, token) 603 const res2 = await getVideosListWithToken(servers[0].url, token)
605 604
606 for (const res of [ res1, res2 ]) { 605 for (const res of [ res1, res2 ]) {
607 const videos: Video[] = res.body.data 606 const videos: Video[] = res.body.data
@@ -619,60 +618,60 @@ describe('Test blocklist', function () {
619 it('Should hide its comments', async function () { 618 it('Should hide its comments', async function () {
620 this.timeout(10000) 619 this.timeout(10000)
621 620
622 const resThreads = await addVideoCommentThread(servers[ 1 ].url, userToken2, videoUUID1, 'hidden comment 2') 621 const resThreads = await addVideoCommentThread(servers[1].url, userToken2, videoUUID1, 'hidden comment 2')
623 const threadId = resThreads.body.comment.id 622 const threadId = resThreads.body.comment.id
624 623
625 await waitJobs(servers) 624 await waitJobs(servers)
626 625
627 await checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 626 await checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
628 627
629 await deleteVideoComment(servers[ 1 ].url, userToken2, videoUUID1, threadId) 628 await deleteVideoComment(servers[1].url, userToken2, videoUUID1, threadId)
630 }) 629 })
631 630
632 it('Should not have notification from blocked instances by instance', async function () { 631 it('Should not have notification from blocked instances by instance', async function () {
633 this.timeout(20000) 632 this.timeout(20000)
634 633
635 { 634 {
636 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' } 635 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' }
637 await checkCommentNotification(servers[ 0 ], comment, 'absence') 636 await checkCommentNotification(servers[0], comment, 'absence')
638 } 637 }
639 638
640 { 639 {
641 const comment = { 640 const comment = {
642 server: servers[ 1 ], 641 server: servers[1],
643 token: userToken2, 642 token: userToken2,
644 videoUUID: videoUUID1, 643 videoUUID: videoUUID1,
645 text: 'hello @root@localhost:' + servers[ 0 ].port 644 text: 'hello @root@localhost:' + servers[0].port
646 } 645 }
647 await checkCommentNotification(servers[ 0 ], comment, 'absence') 646 await checkCommentNotification(servers[0], comment, 'absence')
648 } 647 }
649 }) 648 })
650 649
651 it('Should list blocked servers', async function () { 650 it('Should list blocked servers', async function () {
652 const res = await getServerBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') 651 const res = await getServerBlocklistByServer(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt')
653 const blocks: ServerBlock[] = res.body.data 652 const blocks: ServerBlock[] = res.body.data
654 653
655 expect(res.body.total).to.equal(1) 654 expect(res.body.total).to.equal(1)
656 655
657 const block = blocks[ 0 ] 656 const block = blocks[0]
658 expect(block.byAccount.displayName).to.equal('peertube') 657 expect(block.byAccount.displayName).to.equal('peertube')
659 expect(block.byAccount.name).to.equal('peertube') 658 expect(block.byAccount.name).to.equal('peertube')
660 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) 659 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port)
661 }) 660 })
662 661
663 it('Should unblock the remote server', async function () { 662 it('Should unblock the remote server', async function () {
664 await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) 663 await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
665 }) 664 })
666 665
667 it('Should list all videos', async function () { 666 it('Should list all videos', async function () {
668 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 667 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
669 await checkAllVideos(servers[ 0 ].url, token) 668 await checkAllVideos(servers[0].url, token)
670 } 669 }
671 }) 670 })
672 671
673 it('Should list the comments', async function () { 672 it('Should list the comments', async function () {
674 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 673 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
675 await checkAllComments(servers[ 0 ].url, token, videoUUID1) 674 await checkAllComments(servers[0].url, token, videoUUID1)
676 } 675 }
677 }) 676 })
678 677
@@ -680,18 +679,18 @@ describe('Test blocklist', function () {
680 this.timeout(20000) 679 this.timeout(20000)
681 680
682 { 681 {
683 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } 682 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
684 await checkCommentNotification(servers[ 0 ], comment, 'presence') 683 await checkCommentNotification(servers[0], comment, 'presence')
685 } 684 }
686 685
687 { 686 {
688 const comment = { 687 const comment = {
689 server: servers[ 1 ], 688 server: servers[1],
690 token: userToken2, 689 token: userToken2,
691 videoUUID: videoUUID1, 690 videoUUID: videoUUID1,
692 text: 'hello @root@localhost:' + servers[ 0 ].port 691 text: 'hello @root@localhost:' + servers[0].port
693 } 692 }
694 await checkCommentNotification(servers[ 0 ], comment, 'presence') 693 await checkCommentNotification(servers[0], comment, 'presence')
695 } 694 }
696 }) 695 })
697 }) 696 })
diff --git a/server/tests/api/users/user-subscriptions.ts b/server/tests/api/users/user-subscriptions.ts
index 08017f89c..7d6b0c6a9 100644
--- a/server/tests/api/users/user-subscriptions.ts
+++ b/server/tests/api/users/user-subscriptions.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -13,16 +13,17 @@ import {
13 updateVideo, 13 updateVideo,
14 userLogin 14 userLogin
15} from '../../../../shared/extra-utils' 15} from '../../../../shared/extra-utils'
16import { killallServers, ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index' 16import { ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index'
17import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' 17import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
18import { Video, VideoChannel } from '../../../../shared/models/videos' 18import { Video, VideoChannel } from '../../../../shared/models/videos'
19import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 19import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
20import { 20import {
21 addUserSubscription, 21 addUserSubscription,
22 areSubscriptionsExist,
23 getUserSubscription,
22 listUserSubscriptions, 24 listUserSubscriptions,
23 listUserSubscriptionVideos, 25 listUserSubscriptionVideos,
24 removeUserSubscription, 26 removeUserSubscription
25 getUserSubscription, areSubscriptionsExist
26} from '../../../../shared/extra-utils/users/user-subscriptions' 27} from '../../../../shared/extra-utils/users/user-subscriptions'
27 28
28const expect = chai.expect 29const expect = chai.expect
@@ -116,7 +117,7 @@ describe('Test users subscriptions', function () {
116 117
117 it('Should get subscription', async function () { 118 it('Should get subscription', async function () {
118 { 119 {
119 const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'user3_channel@localhost:' + servers[2].port) 120 const res = await getUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port)
120 const videoChannel: VideoChannel = res.body 121 const videoChannel: VideoChannel = res.body
121 122
122 expect(videoChannel.name).to.equal('user3_channel') 123 expect(videoChannel.name).to.equal('user3_channel')
@@ -127,7 +128,7 @@ describe('Test users subscriptions', function () {
127 } 128 }
128 129
129 { 130 {
130 const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'root_channel@localhost:' + servers[0].port) 131 const res = await getUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:' + servers[0].port)
131 const videoChannel: VideoChannel = res.body 132 const videoChannel: VideoChannel = res.body
132 133
133 expect(videoChannel.name).to.equal('root_channel') 134 expect(videoChannel.name).to.equal('root_channel')
@@ -146,7 +147,7 @@ describe('Test users subscriptions', function () {
146 'user3_channel@localhost:' + servers[0].port 147 'user3_channel@localhost:' + servers[0].port
147 ] 148 ]
148 149
149 const res = await areSubscriptionsExist(servers[ 0 ].url, users[ 0 ].accessToken, uris) 150 const res = await areSubscriptionsExist(servers[0].url, users[0].accessToken, uris)
150 const body = res.body 151 const body = res.body
151 152
152 expect(body['user3_channel@localhost:' + servers[2].port]).to.be.true 153 expect(body['user3_channel@localhost:' + servers[2].port]).to.be.true
diff --git a/server/tests/api/users/users-multiple-servers.ts b/server/tests/api/users/users-multiple-servers.ts
index 791418318..591ce4959 100644
--- a/server/tests/api/users/users-multiple-servers.ts
+++ b/server/tests/api/users/users-multiple-servers.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -57,17 +57,17 @@ describe('Test users with multiple servers', function () {
57 password: 'password' 57 password: 'password'
58 } 58 }
59 const res = await createUser({ 59 const res = await createUser({
60 url: servers[ 0 ].url, 60 url: servers[0].url,
61 accessToken: servers[ 0 ].accessToken, 61 accessToken: servers[0].accessToken,
62 username: user.username, 62 username: user.username,
63 password: user.password 63 password: user.password
64 }) 64 })
65 userId = res.body.user.id 65 userId = res.body.user.id
66 userAccessToken = await userLogin(servers[ 0 ], user) 66 userAccessToken = await userLogin(servers[0], user)
67 } 67 }
68 68
69 { 69 {
70 const resVideo = await uploadVideo(servers[ 0 ].url, userAccessToken, {}) 70 const resVideo = await uploadVideo(servers[0].url, userAccessToken, {})
71 videoUUID = resVideo.body.video.uuid 71 videoUUID = resVideo.body.video.uuid
72 } 72 }
73 73
@@ -86,7 +86,6 @@ describe('Test users with multiple servers', function () {
86 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) 86 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
87 user = res.body 87 user = res.body
88 88
89 const account: Account = user.account
90 expect(user.account.displayName).to.equal('my super display name') 89 expect(user.account.displayName).to.equal('my super display name')
91 90
92 await waitJobs(servers) 91 await waitJobs(servers)
diff --git a/server/tests/api/users/users-verification.ts b/server/tests/api/users/users-verification.ts
index 7cd61f539..675ebf690 100644
--- a/server/tests/api/users/users-verification.ts
+++ b/server/tests/api/users/users-verification.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index 24203a731..502eac0bb 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -1,8 +1,8 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { User, UserRole, Video, MyUser, VideoPlaylistType } from '../../../../shared/index' 5import { MyUser, User, UserRole, Video, VideoPlaylistType } from '../../../../shared/index'
6import { 6import {
7 blockUser, 7 blockUser,
8 cleanupTests, 8 cleanupTests,
@@ -18,7 +18,8 @@ import {
18 getUsersList, 18 getUsersList,
19 getUsersListPaginationAndSort, 19 getUsersListPaginationAndSort,
20 getVideoChannel, 20 getVideoChannel,
21 getVideosList, installPlugin, 21 getVideosList,
22 installPlugin,
22 login, 23 login,
23 makePutBodyRequest, 24 makePutBodyRequest,
24 rateVideo, 25 rateVideo,
@@ -121,13 +122,13 @@ describe('Test users', function () {
121 122
122 it('Should be able to login with an insensitive username', async function () { 123 it('Should be able to login with an insensitive username', async function () {
123 const user = { username: 'RoOt', password: server.user.password } 124 const user = { username: 'RoOt', password: server.user.password }
124 const res = await login(server.url, server.client, user, 200) 125 await login(server.url, server.client, user, 200)
125 126
126 const user2 = { username: 'rOoT', password: server.user.password } 127 const user2 = { username: 'rOoT', password: server.user.password }
127 const res2 = await login(server.url, server.client, user2, 200) 128 await login(server.url, server.client, user2, 200)
128 129
129 const user3 = { username: 'ROOt', password: server.user.password } 130 const user3 = { username: 'ROOt', password: server.user.password }
130 const res3 = await login(server.url, server.client, user3, 200) 131 await login(server.url, server.client, user3, 200)
131 }) 132 })
132 }) 133 })
133 134
@@ -137,7 +138,7 @@ describe('Test users', function () {
137 const videoAttributes = {} 138 const videoAttributes = {}
138 await uploadVideo(server.url, accessToken, videoAttributes) 139 await uploadVideo(server.url, accessToken, videoAttributes)
139 const res = await getVideosList(server.url) 140 const res = await getVideosList(server.url)
140 const video = res.body.data[ 0 ] 141 const video = res.body.data[0]
141 142
142 expect(video.account.name).to.equal('root') 143 expect(video.account.name).to.equal('root')
143 videoId = video.id 144 videoId = video.id
@@ -167,8 +168,8 @@ describe('Test users', function () {
167 const ratings = res.body 168 const ratings = res.body
168 169
169 expect(ratings.total).to.equal(1) 170 expect(ratings.total).to.equal(1)
170 expect(ratings.data[ 0 ].video.id).to.equal(videoId) 171 expect(ratings.data[0].video.id).to.equal(videoId)
171 expect(ratings.data[ 0 ].rating).to.equal('like') 172 expect(ratings.data[0].rating).to.equal('like')
172 }) 173 })
173 174
174 it('Should retrieve ratings list by rating type', async function () { 175 it('Should retrieve ratings list by rating type', async function () {
@@ -307,7 +308,7 @@ describe('Test users', function () {
307 const videos = res.body.data 308 const videos = res.body.data
308 expect(videos).to.have.lengthOf(1) 309 expect(videos).to.have.lengthOf(1)
309 310
310 const video: Video = videos[ 0 ] 311 const video: Video = videos[0]
311 expect(video.name).to.equal('super user video') 312 expect(video.name).to.equal('super user video')
312 expect(video.thumbnailPath).to.not.be.null 313 expect(video.thumbnailPath).to.not.be.null
313 expect(video.previewPath).to.not.be.null 314 expect(video.previewPath).to.not.be.null
@@ -344,12 +345,12 @@ describe('Test users', function () {
344 expect(users).to.be.an('array') 345 expect(users).to.be.an('array')
345 expect(users.length).to.equal(2) 346 expect(users.length).to.equal(2)
346 347
347 const user = users[ 0 ] 348 const user = users[0]
348 expect(user.username).to.equal('user_1') 349 expect(user.username).to.equal('user_1')
349 expect(user.email).to.equal('user_1@example.com') 350 expect(user.email).to.equal('user_1@example.com')
350 expect(user.nsfwPolicy).to.equal('display') 351 expect(user.nsfwPolicy).to.equal('display')
351 352
352 const rootUser = users[ 1 ] 353 const rootUser = users[1]
353 expect(rootUser.username).to.equal('root') 354 expect(rootUser.username).to.equal('root')
354 expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com') 355 expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com')
355 expect(user.nsfwPolicy).to.equal('display') 356 expect(user.nsfwPolicy).to.equal('display')
@@ -367,7 +368,7 @@ describe('Test users', function () {
367 expect(total).to.equal(2) 368 expect(total).to.equal(2)
368 expect(users.length).to.equal(1) 369 expect(users.length).to.equal(1)
369 370
370 const user = users[ 0 ] 371 const user = users[0]
371 expect(user.username).to.equal('root') 372 expect(user.username).to.equal('root')
372 expect(user.email).to.equal('admin' + server.internalServerNumber + '@example.com') 373 expect(user.email).to.equal('admin' + server.internalServerNumber + '@example.com')
373 expect(user.roleLabel).to.equal('Administrator') 374 expect(user.roleLabel).to.equal('Administrator')
@@ -383,7 +384,7 @@ describe('Test users', function () {
383 expect(total).to.equal(2) 384 expect(total).to.equal(2)
384 expect(users.length).to.equal(1) 385 expect(users.length).to.equal(1)
385 386
386 const user = users[ 0 ] 387 const user = users[0]
387 expect(user.username).to.equal('user_1') 388 expect(user.username).to.equal('user_1')
388 expect(user.email).to.equal('user_1@example.com') 389 expect(user.email).to.equal('user_1@example.com')
389 expect(user.nsfwPolicy).to.equal('display') 390 expect(user.nsfwPolicy).to.equal('display')
@@ -398,7 +399,7 @@ describe('Test users', function () {
398 expect(total).to.equal(2) 399 expect(total).to.equal(2)
399 expect(users.length).to.equal(1) 400 expect(users.length).to.equal(1)
400 401
401 const user = users[ 0 ] 402 const user = users[0]
402 expect(user.username).to.equal('user_1') 403 expect(user.username).to.equal('user_1')
403 expect(user.email).to.equal('user_1@example.com') 404 expect(user.email).to.equal('user_1@example.com')
404 expect(user.nsfwPolicy).to.equal('display') 405 expect(user.nsfwPolicy).to.equal('display')
@@ -413,13 +414,13 @@ describe('Test users', function () {
413 expect(total).to.equal(2) 414 expect(total).to.equal(2)
414 expect(users.length).to.equal(2) 415 expect(users.length).to.equal(2)
415 416
416 expect(users[ 0 ].username).to.equal('root') 417 expect(users[0].username).to.equal('root')
417 expect(users[ 0 ].email).to.equal('admin' + server.internalServerNumber + '@example.com') 418 expect(users[0].email).to.equal('admin' + server.internalServerNumber + '@example.com')
418 expect(users[ 0 ].nsfwPolicy).to.equal('display') 419 expect(users[0].nsfwPolicy).to.equal('display')
419 420
420 expect(users[ 1 ].username).to.equal('user_1') 421 expect(users[1].username).to.equal('user_1')
421 expect(users[ 1 ].email).to.equal('user_1@example.com') 422 expect(users[1].email).to.equal('user_1@example.com')
422 expect(users[ 1 ].nsfwPolicy).to.equal('display') 423 expect(users[1].nsfwPolicy).to.equal('display')
423 }) 424 })
424 425
425 it('Should search user by username', async function () { 426 it('Should search user by username', async function () {
@@ -429,7 +430,7 @@ describe('Test users', function () {
429 expect(res.body.total).to.equal(1) 430 expect(res.body.total).to.equal(1)
430 expect(users.length).to.equal(1) 431 expect(users.length).to.equal(1)
431 432
432 expect(users[ 0 ].username).to.equal('root') 433 expect(users[0].username).to.equal('root')
433 }) 434 })
434 435
435 it('Should search user by email', async function () { 436 it('Should search user by email', async function () {
@@ -440,8 +441,8 @@ describe('Test users', function () {
440 expect(res.body.total).to.equal(1) 441 expect(res.body.total).to.equal(1)
441 expect(users.length).to.equal(1) 442 expect(users.length).to.equal(1)
442 443
443 expect(users[ 0 ].username).to.equal('user_1') 444 expect(users[0].username).to.equal('user_1')
444 expect(users[ 0 ].email).to.equal('user_1@example.com') 445 expect(users[0].email).to.equal('user_1@example.com')
445 } 446 }
446 447
447 { 448 {
@@ -451,8 +452,8 @@ describe('Test users', function () {
451 expect(res.body.total).to.equal(2) 452 expect(res.body.total).to.equal(2)
452 expect(users.length).to.equal(2) 453 expect(users.length).to.equal(2)
453 454
454 expect(users[ 0 ].username).to.equal('root') 455 expect(users[0].username).to.equal('root')
455 expect(users[ 1 ].username).to.equal('user_1') 456 expect(users[1].username).to.equal('user_1')
456 } 457 }
457 }) 458 })
458 }) 459 })
@@ -691,7 +692,7 @@ describe('Test users', function () {
691 692
692 expect(res.body.total).to.equal(1) 693 expect(res.body.total).to.equal(1)
693 694
694 const video = res.body.data[ 0 ] 695 const video = res.body.data[0]
695 expect(video.account.name).to.equal('root') 696 expect(video.account.name).to.equal('root')
696 }) 697 })
697 }) 698 })
diff --git a/server/tests/api/videos/audio-only.ts b/server/tests/api/videos/audio-only.ts
index f12d730cc..ac7a0b89c 100644
--- a/server/tests/api/videos/audio-only.ts
+++ b/server/tests/api/videos/audio-only.ts
@@ -1,28 +1,21 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 checkDirectoryIsEmpty,
7 checkSegmentHash,
8 checkTmpIsEmpty,
9 cleanupTests, 6 cleanupTests,
10 doubleFollow, 7 doubleFollow,
11 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
12 getPlaylist, 9 getVideo,
13 getVideo, makeGetRequest, makeRawRequest, 10 root,
14 removeVideo, root,
15 ServerInfo, 11 ServerInfo,
16 setAccessTokensToServers, updateCustomSubConfig, 12 setAccessTokensToServers,
17 updateVideo,
18 uploadVideo, 13 uploadVideo,
19 waitJobs, webtorrentAdd 14 waitJobs
20} from '../../../../shared/extra-utils' 15} from '../../../../shared/extra-utils'
21import { VideoDetails } from '../../../../shared/models/videos' 16import { VideoDetails } from '../../../../shared/models/videos'
22import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type'
23import { join } from 'path' 17import { join } from 'path'
24import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants' 18import { audio, getVideoStreamSize } from '@server/helpers/ffmpeg-utils'
25import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution, audio, getVideoStreamSize } from '@server/helpers/ffmpeg-utils'
26 19
27const expect = chai.expect 20const expect = chai.expect
28 21
@@ -87,14 +80,14 @@ describe('Test audio only video transcoding', function () {
87 80
88 it('0p transcoded video should not have video', async function () { 81 it('0p transcoded video should not have video', async function () {
89 const paths = [ 82 const paths = [
90 join(root(), 'test' + servers[ 0 ].internalServerNumber, 'videos', videoUUID + '-0.mp4'), 83 join(root(), 'test' + servers[0].internalServerNumber, 'videos', videoUUID + '-0.mp4'),
91 join(root(), 'test' + servers[ 0 ].internalServerNumber, 'streaming-playlists', 'hls', videoUUID, videoUUID + '-0-fragmented.mp4') 84 join(root(), 'test' + servers[0].internalServerNumber, 'streaming-playlists', 'hls', videoUUID, videoUUID + '-0-fragmented.mp4')
92 ] 85 ]
93 86
94 for (const path of paths) { 87 for (const path of paths) {
95 const { audioStream } = await audio.get(path) 88 const { audioStream } = await audio.get(path)
96 expect(audioStream[ 'codec_name' ]).to.be.equal('aac') 89 expect(audioStream['codec_name']).to.be.equal('aac')
97 expect(audioStream[ 'bit_rate' ]).to.be.at.most(384 * 8000) 90 expect(audioStream['bit_rate']).to.be.at.most(384 * 8000)
98 91
99 const size = await getVideoStreamSize(path) 92 const size = await getVideoStreamSize(path)
100 expect(size.height).to.equal(0) 93 expect(size.height).to.equal(0)
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index fa3e250ec..e3029f1ae 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -63,9 +63,9 @@ describe('Test multiple servers', function () {
63 displayName: 'my channel', 63 displayName: 'my channel',
64 description: 'super channel' 64 description: 'super channel'
65 } 65 }
66 await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel) 66 await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel)
67 const channelRes = await getVideoChannelsList(servers[ 0 ].url, 0, 1) 67 const channelRes = await getVideoChannelsList(servers[0].url, 0, 1)
68 videoChannelId = channelRes.body.data[ 0 ].id 68 videoChannelId = channelRes.body.data[0].id
69 } 69 }
70 70
71 // Server 1 and server 2 follow each other 71 // Server 1 and server 2 follow each other
@@ -163,7 +163,7 @@ describe('Test multiple servers', function () {
163 username: 'user1', 163 username: 'user1',
164 password: 'super_password' 164 password: 'super_password'
165 } 165 }
166 await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password }) 166 await createUser({ url: servers[1].url, accessToken: servers[1].accessToken, username: user.username, password: user.password })
167 const userAccessToken = await userLogin(servers[1], user) 167 const userAccessToken = await userLogin(servers[1], user)
168 168
169 const videoAttributes = { 169 const videoAttributes = {
@@ -762,12 +762,12 @@ describe('Test multiple servers', function () {
762 762
763 { 763 {
764 const text = 'my super first comment' 764 const text = 'my super first comment'
765 await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, text) 765 await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, text)
766 } 766 }
767 767
768 { 768 {
769 const text = 'my super second comment' 769 const text = 'my super second comment'
770 await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, videoUUID, text) 770 await addVideoCommentThread(servers[2].url, servers[2].accessToken, videoUUID, text)
771 } 771 }
772 772
773 await waitJobs(servers) 773 await waitJobs(servers)
@@ -777,7 +777,7 @@ describe('Test multiple servers', function () {
777 const threadId = res.body.data.find(c => c.text === 'my super first comment').id 777 const threadId = res.body.data.find(c => c.text === 'my super first comment').id
778 778
779 const text = 'my super answer to thread 1' 779 const text = 'my super answer to thread 1'
780 await addVideoCommentReply(servers[ 1 ].url, servers[ 1 ].accessToken, videoUUID, threadId, text) 780 await addVideoCommentReply(servers[1].url, servers[1].accessToken, videoUUID, threadId, text)
781 } 781 }
782 782
783 await waitJobs(servers) 783 await waitJobs(servers)
@@ -790,10 +790,10 @@ describe('Test multiple servers', function () {
790 const childCommentId = res2.body.children[0].comment.id 790 const childCommentId = res2.body.children[0].comment.id
791 791
792 const text3 = 'my second answer to thread 1' 792 const text3 = 'my second answer to thread 1'
793 await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, videoUUID, threadId, text3) 793 await addVideoCommentReply(servers[2].url, servers[2].accessToken, videoUUID, threadId, text3)
794 794
795 const text2 = 'my super answer to answer of thread 1' 795 const text2 = 'my super answer to answer of thread 1'
796 await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, videoUUID, childCommentId, text2) 796 await addVideoCommentReply(servers[2].url, servers[2].accessToken, videoUUID, childCommentId, text2)
797 } 797 }
798 798
799 await waitJobs(servers) 799 await waitJobs(servers)
@@ -900,9 +900,9 @@ describe('Test multiple servers', function () {
900 it('Should delete the thread comments', async function () { 900 it('Should delete the thread comments', async function () {
901 this.timeout(10000) 901 this.timeout(10000)
902 902
903 const res = await getVideoCommentThreads(servers[ 0 ].url, videoUUID, 0, 5) 903 const res = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 5)
904 const threadId = res.body.data.find(c => c.text === 'my super first comment').id 904 const threadId = res.body.data.find(c => c.text === 'my super first comment').id
905 await deleteVideoComment(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, threadId) 905 await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId)
906 906
907 await waitJobs(servers) 907 await waitJobs(servers)
908 }) 908 })
@@ -945,9 +945,9 @@ describe('Test multiple servers', function () {
945 it('Should delete a remote thread by the origin server', async function () { 945 it('Should delete a remote thread by the origin server', async function () {
946 this.timeout(5000) 946 this.timeout(5000)
947 947
948 const res = await getVideoCommentThreads(servers[ 0 ].url, videoUUID, 0, 5) 948 const res = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 5)
949 const threadId = res.body.data.find(c => c.text === 'my super second comment').id 949 const threadId = res.body.data.find(c => c.text === 'my super second comment').id
950 await deleteVideoComment(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, threadId) 950 await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId)
951 951
952 await waitJobs(servers) 952 await waitJobs(servers)
953 }) 953 })
@@ -1021,7 +1021,7 @@ describe('Test multiple servers', function () {
1021 const filePath = join(__dirname, '..', '..', 'fixtures', 'video_short.webm') 1021 const filePath = join(__dirname, '..', '..', 'fixtures', 'video_short.webm')
1022 1022
1023 await req.attach('videofile', filePath) 1023 await req.attach('videofile', filePath)
1024 .expect(200) 1024 .expect(200)
1025 1025
1026 await waitJobs(servers) 1026 await waitJobs(servers)
1027 1027
@@ -1046,7 +1046,7 @@ describe('Test multiple servers', function () {
1046 duration: 5, 1046 duration: 5,
1047 commentsEnabled: true, 1047 commentsEnabled: true,
1048 downloadEnabled: true, 1048 downloadEnabled: true,
1049 tags: [ ], 1049 tags: [],
1050 privacy: VideoPrivacy.PUBLIC, 1050 privacy: VideoPrivacy.PUBLIC,
1051 channel: { 1051 channel: {
1052 displayName: 'Main root channel', 1052 displayName: 'Main root channel',
diff --git a/server/tests/api/videos/services.ts b/server/tests/api/videos/services.ts
index 17172331f..5505a845a 100644
--- a/server/tests/api/videos/services.ts
+++ b/server/tests/api/videos/services.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -31,8 +31,8 @@ describe('Test services', function () {
31 31
32 const res = await getOEmbed(server.url, oembedUrl) 32 const res = await getOEmbed(server.url, oembedUrl)
33 const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + 33 const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
34 `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` + 34 `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` +
35 'frameborder="0" allowfullscreen></iframe>' 35 'frameborder="0" allowfullscreen></iframe>'
36 const expectedThumbnailUrl = 'http://localhost:' + server.port + '/static/previews/' + server.video.uuid + '.jpg' 36 const expectedThumbnailUrl = 'http://localhost:' + server.port + '/static/previews/' + server.video.uuid + '.jpg'
37 37
38 expect(res.body.html).to.equal(expectedHtml) 38 expect(res.body.html).to.equal(expectedHtml)
@@ -53,8 +53,8 @@ describe('Test services', function () {
53 53
54 const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) 54 const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth)
55 const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' + 55 const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' +
56 `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` + 56 `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` +
57 'frameborder="0" allowfullscreen></iframe>' 57 'frameborder="0" allowfullscreen></iframe>'
58 58
59 expect(res.body.html).to.equal(expectedHtml) 59 expect(res.body.html).to.equal(expectedHtml)
60 expect(res.body.title).to.equal(server.video.name) 60 expect(res.body.title).to.equal(server.video.name)
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts
index 362d6b78f..596fff996 100644
--- a/server/tests/api/videos/single-server.ts
+++ b/server/tests/api/videos/single-server.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import { keyBy } from 'lodash' 4import { keyBy } from 'lodash'
diff --git a/server/tests/api/videos/video-abuse.ts b/server/tests/api/videos/video-abuse.ts
index 0cd6f22c7..cd6df7267 100644
--- a/server/tests/api/videos/video-abuse.ts
+++ b/server/tests/api/videos/video-abuse.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -183,9 +183,9 @@ describe('Test video abuses', function () {
183 const accountToBlock = 'root@localhost:' + servers[1].port 183 const accountToBlock = 'root@localhost:' + servers[1].port
184 184
185 { 185 {
186 await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock) 186 await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock)
187 187
188 const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) 188 const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
189 expect(res.body.total).to.equal(2) 189 expect(res.body.total).to.equal(2)
190 190
191 const abuse = res.body.data.find(a => a.reason === 'will mute this') 191 const abuse = res.body.data.find(a => a.reason === 'will mute this')
@@ -193,9 +193,9 @@ describe('Test video abuses', function () {
193 } 193 }
194 194
195 { 195 {
196 await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock) 196 await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock)
197 197
198 const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) 198 const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
199 expect(res.body.total).to.equal(3) 199 expect(res.body.total).to.equal(3)
200 } 200 }
201 }) 201 })
@@ -204,9 +204,9 @@ describe('Test video abuses', function () {
204 const serverToBlock = servers[1].host 204 const serverToBlock = servers[1].host
205 205
206 { 206 {
207 await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, servers[1].host) 207 await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, servers[1].host)
208 208
209 const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) 209 const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
210 expect(res.body.total).to.equal(2) 210 expect(res.body.total).to.equal(2)
211 211
212 const abuse = res.body.data.find(a => a.reason === 'will mute this') 212 const abuse = res.body.data.find(a => a.reason === 'will mute this')
@@ -214,9 +214,9 @@ describe('Test video abuses', function () {
214 } 214 }
215 215
216 { 216 {
217 await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, serverToBlock) 217 await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, serverToBlock)
218 218
219 const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) 219 const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
220 expect(res.body.total).to.equal(3) 220 expect(res.body.total).to.equal(3)
221 } 221 }
222 }) 222 })
diff --git a/server/tests/api/videos/video-blacklist.ts b/server/tests/api/videos/video-blacklist.ts
index 854b2f0cb..67bc0114c 100644
--- a/server/tests/api/videos/video-blacklist.ts
+++ b/server/tests/api/videos/video-blacklist.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import { orderBy } from 'lodash' 4import { orderBy } from 'lodash'
@@ -8,7 +8,8 @@ import {
8 cleanupTests, 8 cleanupTests,
9 createUser, 9 createUser,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
11 getBlacklistedVideosList, getMyUserInformation, 11 getBlacklistedVideosList,
12 getMyUserInformation,
12 getMyVideos, 13 getMyVideos,
13 getVideosList, 14 getVideosList,
14 killallServers, 15 killallServers,
@@ -17,7 +18,6 @@ import {
17 searchVideo, 18 searchVideo,
18 ServerInfo, 19 ServerInfo,
19 setAccessTokensToServers, 20 setAccessTokensToServers,
20 setDefaultVideoChannel,
21 updateVideo, 21 updateVideo,
22 updateVideoBlacklist, 22 updateVideoBlacklist,
23 uploadVideo, 23 uploadVideo,
@@ -27,7 +27,7 @@ import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
27import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 27import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
28import { VideoBlacklist, VideoBlacklistType } from '../../../../shared/models/videos' 28import { VideoBlacklist, VideoBlacklistType } from '../../../../shared/models/videos'
29import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' 29import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
30import { User, UserRole, UserUpdateMe } from '../../../../shared/models/users' 30import { User, UserRole } from '../../../../shared/models/users'
31import { getMagnetURI, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports' 31import { getMagnetURI, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports'
32 32
33const expect = chai.expect 33const expect = chai.expect
@@ -40,7 +40,7 @@ describe('Test video blacklist', function () {
40 const res = await getVideosList(server.url) 40 const res = await getVideosList(server.url)
41 41
42 const videos = res.body.data 42 const videos = res.body.data
43 for (let video of videos) { 43 for (const video of videos) {
44 await addVideoToBlacklist(server.url, server.accessToken, video.id, 'super reason') 44 await addVideoToBlacklist(server.url, server.accessToken, video.id, 'super reason')
45 } 45 }
46 } 46 }
@@ -72,7 +72,7 @@ describe('Test video blacklist', function () {
72 72
73 it('Should not have the video blacklisted in videos list/search on server 1', async function () { 73 it('Should not have the video blacklisted in videos list/search on server 1', async function () {
74 { 74 {
75 const res = await getVideosList(servers[ 0 ].url) 75 const res = await getVideosList(servers[0].url)
76 76
77 expect(res.body.total).to.equal(0) 77 expect(res.body.total).to.equal(0)
78 expect(res.body.data).to.be.an('array') 78 expect(res.body.data).to.be.an('array')
@@ -80,7 +80,7 @@ describe('Test video blacklist', function () {
80 } 80 }
81 81
82 { 82 {
83 const res = await searchVideo(servers[ 0 ].url, 'name') 83 const res = await searchVideo(servers[0].url, 'name')
84 84
85 expect(res.body.total).to.equal(0) 85 expect(res.body.total).to.equal(0)
86 expect(res.body.data).to.be.an('array') 86 expect(res.body.data).to.be.an('array')
@@ -90,7 +90,7 @@ describe('Test video blacklist', function () {
90 90
91 it('Should have the blacklisted video in videos list/search on server 2', async function () { 91 it('Should have the blacklisted video in videos list/search on server 2', async function () {
92 { 92 {
93 const res = await getVideosList(servers[ 1 ].url) 93 const res = await getVideosList(servers[1].url)
94 94
95 expect(res.body.total).to.equal(2) 95 expect(res.body.total).to.equal(2)
96 expect(res.body.data).to.be.an('array') 96 expect(res.body.data).to.be.an('array')
@@ -98,7 +98,7 @@ describe('Test video blacklist', function () {
98 } 98 }
99 99
100 { 100 {
101 const res = await searchVideo(servers[ 1 ].url, 'video') 101 const res = await searchVideo(servers[1].url, 'video')
102 102
103 expect(res.body.total).to.equal(2) 103 expect(res.body.total).to.equal(2)
104 expect(res.body.data).to.be.an('array') 104 expect(res.body.data).to.be.an('array')
@@ -125,8 +125,8 @@ describe('Test video blacklist', function () {
125 125
126 it('Should display all the blacklisted videos when applying manual type filter', async function () { 126 it('Should display all the blacklisted videos when applying manual type filter', async function () {
127 const res = await getBlacklistedVideosList({ 127 const res = await getBlacklistedVideosList({
128 url: servers[ 0 ].url, 128 url: servers[0].url,
129 token: servers[ 0 ].accessToken, 129 token: servers[0].accessToken,
130 type: VideoBlacklistType.MANUAL 130 type: VideoBlacklistType.MANUAL
131 }) 131 })
132 132
@@ -139,8 +139,8 @@ describe('Test video blacklist', function () {
139 139
140 it('Should display nothing when applying automatic type filter', async function () { 140 it('Should display nothing when applying automatic type filter', async function () {
141 const res = await getBlacklistedVideosList({ 141 const res = await getBlacklistedVideosList({
142 url: servers[ 0 ].url, 142 url: servers[0].url,
143 token: servers[ 0 ].accessToken, 143 token: servers[0].accessToken,
144 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED 144 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
145 }) 145 })
146 146
@@ -152,7 +152,7 @@ describe('Test video blacklist', function () {
152 }) 152 })
153 153
154 it('Should get the correct sort when sorting by descending id', async function () { 154 it('Should get the correct sort when sorting by descending id', async function () {
155 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-id' }) 155 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-id' })
156 expect(res.body.total).to.equal(2) 156 expect(res.body.total).to.equal(2)
157 157
158 const blacklistedVideos = res.body.data 158 const blacklistedVideos = res.body.data
@@ -165,7 +165,7 @@ describe('Test video blacklist', function () {
165 }) 165 })
166 166
167 it('Should get the correct sort when sorting by descending video name', async function () { 167 it('Should get the correct sort when sorting by descending video name', async function () {
168 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) 168 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-name' })
169 expect(res.body.total).to.equal(2) 169 expect(res.body.total).to.equal(2)
170 170
171 const blacklistedVideos = res.body.data 171 const blacklistedVideos = res.body.data
@@ -178,7 +178,7 @@ describe('Test video blacklist', function () {
178 }) 178 })
179 179
180 it('Should get the correct sort when sorting by ascending creation date', async function () { 180 it('Should get the correct sort when sorting by ascending creation date', async function () {
181 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: 'createdAt' }) 181 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: 'createdAt' })
182 expect(res.body.total).to.equal(2) 182 expect(res.body.total).to.equal(2)
183 183
184 const blacklistedVideos = res.body.data 184 const blacklistedVideos = res.body.data
@@ -195,7 +195,7 @@ describe('Test video blacklist', function () {
195 it('Should change the reason', async function () { 195 it('Should change the reason', async function () {
196 await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated') 196 await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated')
197 197
198 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) 198 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-name' })
199 const video = res.body.data.find(b => b.video.id === videoId) 199 const video = res.body.data.find(b => b.video.id === videoId)
200 200
201 expect(video.reason).to.equal('my super reason updated') 201 expect(video.reason).to.equal('my super reason updated')
@@ -231,7 +231,7 @@ describe('Test video blacklist', function () {
231 231
232 it('Should remove a video from the blacklist on server 1', async function () { 232 it('Should remove a video from the blacklist on server 1', async function () {
233 // Get one video in the blacklist 233 // Get one video in the blacklist
234 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) 234 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-name' })
235 videoToRemove = res.body.data[0] 235 videoToRemove = res.body.data[0]
236 blacklist = res.body.data.slice(1) 236 blacklist = res.body.data.slice(1)
237 237
@@ -252,7 +252,7 @@ describe('Test video blacklist', function () {
252 }) 252 })
253 253
254 it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () { 254 it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () {
255 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) 255 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-name' })
256 expect(res.body.total).to.equal(1) 256 expect(res.body.total).to.equal(1)
257 257
258 const videos = res.body.data 258 const videos = res.body.data
@@ -274,7 +274,7 @@ describe('Test video blacklist', function () {
274 video3UUID = res.body.video.uuid 274 video3UUID = res.body.video.uuid
275 } 275 }
276 { 276 {
277 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'Video 4' }) 277 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'Video 4' })
278 video4UUID = res.body.video.uuid 278 video4UUID = res.body.video.uuid
279 } 279 }
280 280
@@ -284,17 +284,17 @@ describe('Test video blacklist', function () {
284 it('Should blacklist video 3 and keep it federated', async function () { 284 it('Should blacklist video 3 and keep it federated', async function () {
285 this.timeout(10000) 285 this.timeout(10000)
286 286
287 await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video3UUID, 'super reason', false) 287 await addVideoToBlacklist(servers[0].url, servers[0].accessToken, video3UUID, 'super reason', false)
288 288
289 await waitJobs(servers) 289 await waitJobs(servers)
290 290
291 { 291 {
292 const res = await getVideosList(servers[ 0 ].url) 292 const res = await getVideosList(servers[0].url)
293 expect(res.body.data.find(v => v.uuid === video3UUID)).to.be.undefined 293 expect(res.body.data.find(v => v.uuid === video3UUID)).to.be.undefined
294 } 294 }
295 295
296 { 296 {
297 const res = await getVideosList(servers[ 1 ].url) 297 const res = await getVideosList(servers[1].url)
298 expect(res.body.data.find(v => v.uuid === video3UUID)).to.not.be.undefined 298 expect(res.body.data.find(v => v.uuid === video3UUID)).to.not.be.undefined
299 } 299 }
300 }) 300 })
@@ -302,7 +302,7 @@ describe('Test video blacklist', function () {
302 it('Should unfederate the video', async function () { 302 it('Should unfederate the video', async function () {
303 this.timeout(10000) 303 this.timeout(10000)
304 304
305 await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video4UUID, 'super reason', true) 305 await addVideoToBlacklist(servers[0].url, servers[0].accessToken, video4UUID, 'super reason', true)
306 306
307 await waitJobs(servers) 307 await waitJobs(servers)
308 308
@@ -315,7 +315,7 @@ describe('Test video blacklist', function () {
315 it('Should have the video unfederated even after an Update AP message', async function () { 315 it('Should have the video unfederated even after an Update AP message', async function () {
316 this.timeout(10000) 316 this.timeout(10000)
317 317
318 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video4UUID, { description: 'super description' }) 318 await updateVideo(servers[0].url, servers[0].accessToken, video4UUID, { description: 'super description' })
319 319
320 await waitJobs(servers) 320 await waitJobs(servers)
321 321
@@ -326,7 +326,7 @@ describe('Test video blacklist', function () {
326 }) 326 })
327 327
328 it('Should have the correct video blacklist unfederate attribute', async function () { 328 it('Should have the correct video blacklist unfederate attribute', async function () {
329 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: 'createdAt' }) 329 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: 'createdAt' })
330 330
331 const blacklistedVideos: VideoBlacklist[] = res.body.data 331 const blacklistedVideos: VideoBlacklist[] = res.body.data
332 const video3Blacklisted = blacklistedVideos.find(b => b.video.uuid === video3UUID) 332 const video3Blacklisted = blacklistedVideos.find(b => b.video.uuid === video3UUID)
@@ -339,7 +339,7 @@ describe('Test video blacklist', function () {
339 it('Should remove the video from blacklist and refederate the video', async function () { 339 it('Should remove the video from blacklist and refederate the video', async function () {
340 this.timeout(10000) 340 this.timeout(10000)
341 341
342 await removeVideoFromBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video4UUID) 342 await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, video4UUID)
343 343
344 await waitJobs(servers) 344 await waitJobs(servers)
345 345
@@ -362,9 +362,9 @@ describe('Test video blacklist', function () {
362 killallServers([ servers[0] ]) 362 killallServers([ servers[0] ])
363 363
364 const config = { 364 const config = {
365 'auto_blacklist': { 365 auto_blacklist: {
366 videos: { 366 videos: {
367 'of_users': { 367 of_users: {
368 enabled: true 368 enabled: true
369 } 369 }
370 } 370 }
@@ -375,8 +375,8 @@ describe('Test video blacklist', function () {
375 { 375 {
376 const user = { username: 'user_without_flag', password: 'password' } 376 const user = { username: 'user_without_flag', password: 'password' }
377 await createUser({ 377 await createUser({
378 url: servers[ 0 ].url, 378 url: servers[0].url,
379 accessToken: servers[ 0 ].accessToken, 379 accessToken: servers[0].accessToken,
380 username: user.username, 380 username: user.username,
381 adminFlags: UserAdminFlag.NONE, 381 adminFlags: UserAdminFlag.NONE,
382 password: user.password, 382 password: user.password,
@@ -393,8 +393,8 @@ describe('Test video blacklist', function () {
393 { 393 {
394 const user = { username: 'user_with_flag', password: 'password' } 394 const user = { username: 'user_with_flag', password: 'password' }
395 await createUser({ 395 await createUser({
396 url: servers[ 0 ].url, 396 url: servers[0].url,
397 accessToken: servers[ 0 ].accessToken, 397 accessToken: servers[0].accessToken,
398 username: user.username, 398 username: user.username,
399 adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST, 399 adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST,
400 password: user.password, 400 password: user.password,
@@ -411,8 +411,8 @@ describe('Test video blacklist', function () {
411 await uploadVideo(servers[0].url, userWithoutFlag, { name: 'blacklisted' }) 411 await uploadVideo(servers[0].url, userWithoutFlag, { name: 'blacklisted' })
412 412
413 const res = await getBlacklistedVideosList({ 413 const res = await getBlacklistedVideosList({
414 url: servers[ 0 ].url, 414 url: servers[0].url,
415 token: servers[ 0 ].accessToken, 415 token: servers[0].accessToken,
416 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED 416 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
417 }) 417 })
418 418
@@ -428,11 +428,11 @@ describe('Test video blacklist', function () {
428 name: 'URL import', 428 name: 'URL import',
429 channelId: channelOfUserWithoutFlag 429 channelId: channelOfUserWithoutFlag
430 } 430 }
431 await importVideo(servers[ 0 ].url, userWithoutFlag, attributes) 431 await importVideo(servers[0].url, userWithoutFlag, attributes)
432 432
433 const res = await getBlacklistedVideosList({ 433 const res = await getBlacklistedVideosList({
434 url: servers[ 0 ].url, 434 url: servers[0].url,
435 token: servers[ 0 ].accessToken, 435 token: servers[0].accessToken,
436 sort: 'createdAt', 436 sort: 'createdAt',
437 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED 437 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
438 }) 438 })
@@ -447,11 +447,11 @@ describe('Test video blacklist', function () {
447 name: 'Torrent import', 447 name: 'Torrent import',
448 channelId: channelOfUserWithoutFlag 448 channelId: channelOfUserWithoutFlag
449 } 449 }
450 await importVideo(servers[ 0 ].url, userWithoutFlag, attributes) 450 await importVideo(servers[0].url, userWithoutFlag, attributes)
451 451
452 const res = await getBlacklistedVideosList({ 452 const res = await getBlacklistedVideosList({
453 url: servers[ 0 ].url, 453 url: servers[0].url,
454 token: servers[ 0 ].accessToken, 454 token: servers[0].accessToken,
455 sort: 'createdAt', 455 sort: 'createdAt',
456 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED 456 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
457 }) 457 })
@@ -464,8 +464,8 @@ describe('Test video blacklist', function () {
464 await uploadVideo(servers[0].url, userWithFlag, { name: 'not blacklisted' }) 464 await uploadVideo(servers[0].url, userWithFlag, { name: 'not blacklisted' })
465 465
466 const res = await getBlacklistedVideosList({ 466 const res = await getBlacklistedVideosList({
467 url: servers[ 0 ].url, 467 url: servers[0].url,
468 token: servers[ 0 ].accessToken, 468 token: servers[0].accessToken,
469 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED 469 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
470 }) 470 })
471 471
diff --git a/server/tests/api/videos/video-captions.ts b/server/tests/api/videos/video-captions.ts
index 5e13f5949..b4ecb39f4 100644
--- a/server/tests/api/videos/video-captions.ts
+++ b/server/tests/api/videos/video-captions.ts
@@ -1,16 +1,17 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 checkVideoFilesWereRemoved, cleanupTests, 6 checkVideoFilesWereRemoved,
7 cleanupTests,
7 doubleFollow, 8 doubleFollow,
8 flushAndRunMultipleServers, 9 flushAndRunMultipleServers,
9 removeVideo, 10 removeVideo,
10 uploadVideo, 11 uploadVideo,
11 wait 12 wait
12} from '../../../../shared/extra-utils' 13} from '../../../../shared/extra-utils'
13import { flushTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index' 14import { ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index'
14import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 15import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
15import { 16import {
16 createVideoCaption, 17 createVideoCaption,
@@ -36,7 +37,7 @@ describe('Test video captions', function () {
36 37
37 await waitJobs(servers) 38 await waitJobs(servers)
38 39
39 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'my video name' }) 40 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'my video name' })
40 videoUUID = res.body.video.uuid 41 videoUUID = res.body.video.uuid
41 42
42 await waitJobs(servers) 43 await waitJobs(servers)
diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts
index 64ee2355a..dee6575b9 100644
--- a/server/tests/api/videos/video-change-ownership.ts
+++ b/server/tests/api/videos/video-change-ownership.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -38,7 +38,7 @@ describe('Test video change ownership - nominal', function () {
38 } 38 }
39 let firstUserAccessToken = '' 39 let firstUserAccessToken = ''
40 let secondUserAccessToken = '' 40 let secondUserAccessToken = ''
41 let lastRequestChangeOwnershipId = undefined 41 let lastRequestChangeOwnershipId = ''
42 42
43 before(async function () { 43 before(async function () {
44 this.timeout(50000) 44 this.timeout(50000)
@@ -48,15 +48,15 @@ describe('Test video change ownership - nominal', function () {
48 48
49 const videoQuota = 42000000 49 const videoQuota = 42000000
50 await createUser({ 50 await createUser({
51 url: servers[ 0 ].url, 51 url: servers[0].url,
52 accessToken: servers[ 0 ].accessToken, 52 accessToken: servers[0].accessToken,
53 username: firstUser.username, 53 username: firstUser.username,
54 password: firstUser.password, 54 password: firstUser.password,
55 videoQuota: videoQuota 55 videoQuota: videoQuota
56 }) 56 })
57 await createUser({ 57 await createUser({
58 url: servers[ 0 ].url, 58 url: servers[0].url,
59 accessToken: servers[ 0 ].accessToken, 59 accessToken: servers[0].accessToken,
60 username: secondUser.username, 60 username: secondUser.username,
61 password: secondUser.password, 61 password: secondUser.password,
62 videoQuota: videoQuota 62 videoQuota: videoQuota
@@ -209,7 +209,7 @@ describe('Test video change ownership - nominal', function () {
209}) 209})
210 210
211describe('Test video change ownership - quota too small', function () { 211describe('Test video change ownership - quota too small', function () {
212 let server: ServerInfo = undefined 212 let server: ServerInfo
213 const firstUser = { 213 const firstUser = {
214 username: 'first', 214 username: 'first',
215 password: 'My great password' 215 password: 'My great password'
@@ -220,14 +220,14 @@ describe('Test video change ownership - quota too small', function () {
220 } 220 }
221 let firstUserAccessToken = '' 221 let firstUserAccessToken = ''
222 let secondUserAccessToken = '' 222 let secondUserAccessToken = ''
223 let lastRequestChangeOwnershipId = undefined 223 let lastRequestChangeOwnershipId = ''
224 224
225 before(async function () { 225 before(async function () {
226 this.timeout(50000) 226 this.timeout(50000)
227 227
228 // Run one server 228 // Run one server
229 server = await flushAndRunServer(1) 229 server = await flushAndRunServer(1)
230 await setAccessTokensToServers([server]) 230 await setAccessTokensToServers([ server ])
231 231
232 const videoQuota = 42000000 232 const videoQuota = 42000000
233 const limitedVideoQuota = 10 233 const limitedVideoQuota = 10
diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts
index 4f600cae8..f3a23bf17 100644
--- a/server/tests/api/videos/video-channels.ts
+++ b/server/tests/api/videos/video-channels.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -7,7 +7,8 @@ import {
7 cleanupTests, 7 cleanupTests,
8 createUser, 8 createUser,
9 doubleFollow, 9 doubleFollow,
10 flushAndRunMultipleServers, getVideo, 10 flushAndRunMultipleServers,
11 getVideo,
11 getVideoChannelVideos, 12 getVideoChannelVideos,
12 testImage, 13 testImage,
13 updateVideo, 14 updateVideo,
@@ -73,14 +74,14 @@ describe('Test video channels', function () {
73 description: 'super video channel description', 74 description: 'super video channel description',
74 support: 'super video channel support text' 75 support: 'super video channel support text'
75 } 76 }
76 const res = await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel) 77 const res = await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel)
77 secondVideoChannelId = res.body.videoChannel.id 78 secondVideoChannelId = res.body.videoChannel.id
78 } 79 }
79 80
80 // The channel is 1 is propagated to servers 2 81 // The channel is 1 is propagated to servers 2
81 { 82 {
82 const videoAttributesArg = { name: 'my video name', channelId: secondVideoChannelId, support: 'video support field' } 83 const videoAttributesArg = { name: 'my video name', channelId: secondVideoChannelId, support: 'video support field' }
83 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributesArg) 84 const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributesArg)
84 videoUUID = res.body.video.uuid 85 videoUUID = res.body.video.uuid
85 } 86 }
86 87
@@ -106,7 +107,7 @@ describe('Test video channels', function () {
106 107
107 it('Should have two video channels when getting account channels on server 1', async function () { 108 it('Should have two video channels when getting account channels on server 1', async function () {
108 const res = await getAccountVideoChannelsList({ 109 const res = await getAccountVideoChannelsList({
109 url: servers[ 0 ].url, 110 url: servers[0].url,
110 accountName: userInfo.account.name + '@' + userInfo.account.host 111 accountName: userInfo.account.name + '@' + userInfo.account.host
111 }) 112 })
112 113
@@ -127,7 +128,7 @@ describe('Test video channels', function () {
127 it('Should paginate and sort account channels', async function () { 128 it('Should paginate and sort account channels', async function () {
128 { 129 {
129 const res = await getAccountVideoChannelsList({ 130 const res = await getAccountVideoChannelsList({
130 url: servers[ 0 ].url, 131 url: servers[0].url,
131 accountName: userInfo.account.name + '@' + userInfo.account.host, 132 accountName: userInfo.account.name + '@' + userInfo.account.host,
132 start: 0, 133 start: 0,
133 count: 1, 134 count: 1,
@@ -137,13 +138,13 @@ describe('Test video channels', function () {
137 expect(res.body.total).to.equal(2) 138 expect(res.body.total).to.equal(2)
138 expect(res.body.data).to.have.lengthOf(1) 139 expect(res.body.data).to.have.lengthOf(1)
139 140
140 const videoChannel: VideoChannel = res.body.data[ 0 ] 141 const videoChannel: VideoChannel = res.body.data[0]
141 expect(videoChannel.name).to.equal('root_channel') 142 expect(videoChannel.name).to.equal('root_channel')
142 } 143 }
143 144
144 { 145 {
145 const res = await getAccountVideoChannelsList({ 146 const res = await getAccountVideoChannelsList({
146 url: servers[ 0 ].url, 147 url: servers[0].url,
147 accountName: userInfo.account.name + '@' + userInfo.account.host, 148 accountName: userInfo.account.name + '@' + userInfo.account.host,
148 start: 0, 149 start: 0,
149 count: 1, 150 count: 1,
@@ -153,13 +154,13 @@ describe('Test video channels', function () {
153 expect(res.body.total).to.equal(2) 154 expect(res.body.total).to.equal(2)
154 expect(res.body.data).to.have.lengthOf(1) 155 expect(res.body.data).to.have.lengthOf(1)
155 156
156 const videoChannel: VideoChannel = res.body.data[ 0 ] 157 const videoChannel: VideoChannel = res.body.data[0]
157 expect(videoChannel.name).to.equal('second_video_channel') 158 expect(videoChannel.name).to.equal('second_video_channel')
158 } 159 }
159 160
160 { 161 {
161 const res = await getAccountVideoChannelsList({ 162 const res = await getAccountVideoChannelsList({
162 url: servers[ 0 ].url, 163 url: servers[0].url,
163 accountName: userInfo.account.name + '@' + userInfo.account.host, 164 accountName: userInfo.account.name + '@' + userInfo.account.host,
164 start: 1, 165 start: 1,
165 count: 1, 166 count: 1,
@@ -169,14 +170,14 @@ describe('Test video channels', function () {
169 expect(res.body.total).to.equal(2) 170 expect(res.body.total).to.equal(2)
170 expect(res.body.data).to.have.lengthOf(1) 171 expect(res.body.data).to.have.lengthOf(1)
171 172
172 const videoChannel: VideoChannel = res.body.data[ 0 ] 173 const videoChannel: VideoChannel = res.body.data[0]
173 expect(videoChannel.name).to.equal('root_channel') 174 expect(videoChannel.name).to.equal('root_channel')
174 } 175 }
175 }) 176 })
176 177
177 it('Should have one video channel when getting account channels on server 2', async function () { 178 it('Should have one video channel when getting account channels on server 2', async function () {
178 const res = await getAccountVideoChannelsList({ 179 const res = await getAccountVideoChannelsList({
179 url: servers[ 1 ].url, 180 url: servers[1].url,
180 accountName: userInfo.account.name + '@' + userInfo.account.host 181 accountName: userInfo.account.name + '@' + userInfo.account.host
181 }) 182 })
182 183
@@ -349,15 +350,15 @@ describe('Test video channels', function () {
349 it('Should create the main channel with an uuid if there is a conflict', async function () { 350 it('Should create the main channel with an uuid if there is a conflict', async function () {
350 { 351 {
351 const videoChannel = { name: 'toto_channel', displayName: 'My toto channel' } 352 const videoChannel = { name: 'toto_channel', displayName: 'My toto channel' }
352 await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel) 353 await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel)
353 } 354 }
354 355
355 { 356 {
356 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: 'toto', password: 'password' }) 357 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: 'toto', password: 'password' })
357 const accessToken = await userLogin(servers[ 0 ], { username: 'toto', password: 'password' }) 358 const accessToken = await userLogin(servers[0], { username: 'toto', password: 'password' })
358 359
359 const res = await getMyUserInformation(servers[ 0 ].url, accessToken) 360 const res = await getMyUserInformation(servers[0].url, accessToken)
360 const videoChannel = res.body.videoChannels[ 0 ] 361 const videoChannel = res.body.videoChannels[0]
361 expect(videoChannel.name).to.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/) 362 expect(videoChannel.name).to.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/)
362 } 363 }
363 }) 364 })
diff --git a/server/tests/api/videos/video-comments.ts b/server/tests/api/videos/video-comments.ts
index 06e4ff9c8..afb58e95a 100644
--- a/server/tests/api/videos/video-comments.ts
+++ b/server/tests/api/videos/video-comments.ts
@@ -1,17 +1,17 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' 5import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
6import { cleanupTests, testImage } from '../../../../shared/extra-utils' 6import { cleanupTests, testImage } from '../../../../shared/extra-utils'
7import { 7import {
8 createUser,
8 dateIsValid, 9 dateIsValid,
9 flushAndRunServer, 10 flushAndRunServer,
11 getAccessToken,
10 ServerInfo, 12 ServerInfo,
11 setAccessTokensToServers, 13 setAccessTokensToServers,
12 updateMyAvatar, 14 updateMyAvatar,
13 getAccessToken,
14 createUser,
15 uploadVideo 15 uploadVideo
16} from '../../../../shared/extra-utils/index' 16} from '../../../../shared/extra-utils/index'
17import { 17import {
diff --git a/server/tests/api/videos/video-description.ts b/server/tests/api/videos/video-description.ts
index db4d278bf..b8e98e45f 100644
--- a/server/tests/api/videos/video-description.ts
+++ b/server/tests/api/videos/video-description.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -8,7 +8,6 @@ import {
8 getVideo, 8 getVideo,
9 getVideoDescription, 9 getVideoDescription,
10 getVideosList, 10 getVideosList,
11 killallServers,
12 ServerInfo, 11 ServerInfo,
13 setAccessTokensToServers, 12 setAccessTokensToServers,
14 updateVideo, 13 updateVideo,
@@ -23,7 +22,7 @@ describe('Test video description', function () {
23 let servers: ServerInfo[] = [] 22 let servers: ServerInfo[] = []
24 let videoUUID = '' 23 let videoUUID = ''
25 let videoId: number 24 let videoId: number
26 let longDescription = 'my super description for server 1'.repeat(50) 25 const longDescription = 'my super description for server 1'.repeat(50)
27 26
28 before(async function () { 27 before(async function () {
29 this.timeout(40000) 28 this.timeout(40000)
@@ -61,7 +60,7 @@ describe('Test video description', function () {
61 60
62 // 30 characters * 6 -> 240 characters 61 // 30 characters * 6 -> 240 characters
63 const truncatedDescription = 'my super description for server 1'.repeat(7) + 62 const truncatedDescription = 'my super description for server 1'.repeat(7) +
64 'my super descrip...' 63 'my super descrip...'
65 64
66 expect(video.description).to.equal(truncatedDescription) 65 expect(video.description).to.equal(truncatedDescription)
67 } 66 }
diff --git a/server/tests/api/videos/video-hls.ts b/server/tests/api/videos/video-hls.ts
index bde3b5656..6555bc8b6 100644
--- a/server/tests/api/videos/video-hls.ts
+++ b/server/tests/api/videos/video-hls.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -10,13 +10,16 @@ import {
10 doubleFollow, 10 doubleFollow,
11 flushAndRunMultipleServers, 11 flushAndRunMultipleServers,
12 getPlaylist, 12 getPlaylist,
13 getVideo, makeGetRequest, makeRawRequest, 13 getVideo,
14 makeRawRequest,
14 removeVideo, 15 removeVideo,
15 ServerInfo, 16 ServerInfo,
16 setAccessTokensToServers, updateCustomSubConfig, 17 setAccessTokensToServers,
18 updateCustomSubConfig,
17 updateVideo, 19 updateVideo,
18 uploadVideo, 20 uploadVideo,
19 waitJobs, webtorrentAdd 21 waitJobs,
22 webtorrentAdd
20} from '../../../../shared/extra-utils' 23} from '../../../../shared/extra-utils'
21import { VideoDetails } from '../../../../shared/models/videos' 24import { VideoDetails } from '../../../../shared/models/videos'
22import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' 25import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type'
@@ -48,7 +51,9 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn
48 51
49 expect(file.magnetUri).to.have.lengthOf.above(2) 52 expect(file.magnetUri).to.have.lengthOf.above(2)
50 expect(file.torrentUrl).to.equal(`${baseUrl}/static/torrents/${videoDetails.uuid}-${file.resolution.id}-hls.torrent`) 53 expect(file.torrentUrl).to.equal(`${baseUrl}/static/torrents/${videoDetails.uuid}-${file.resolution.id}-hls.torrent`)
51 expect(file.fileUrl).to.equal(`${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${videoDetails.uuid}-${file.resolution.id}-fragmented.mp4`) 54 expect(file.fileUrl).to.equal(
55 `${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${videoDetails.uuid}-${file.resolution.id}-fragmented.mp4`
56 )
52 expect(file.resolution.label).to.equal(resolution + 'p') 57 expect(file.resolution.label).to.equal(resolution + 'p')
53 58
54 await makeRawRequest(file.torrentUrl, 200) 59 await makeRawRequest(file.torrentUrl, 200)
@@ -66,7 +71,9 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn
66 const masterPlaylist = res.text 71 const masterPlaylist = res.text
67 72
68 for (const resolution of resolutions) { 73 for (const resolution of resolutions) {
69 const reg = new RegExp('#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',FRAME-RATE=\\d+,CODECS="avc1.64001f,mp4a.40.2"') 74 const reg = new RegExp(
75 '#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',FRAME-RATE=\\d+,CODECS="avc1.64001f,mp4a.40.2"'
76 )
70 77
71 expect(masterPlaylist).to.match(reg) 78 expect(masterPlaylist).to.match(reg)
72 expect(masterPlaylist).to.contain(`${resolution}.m3u8`) 79 expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
@@ -102,7 +109,7 @@ describe('Test HLS videos', function () {
102 it('Should upload a video and transcode it to HLS', async function () { 109 it('Should upload a video and transcode it to HLS', async function () {
103 this.timeout(120000) 110 this.timeout(120000)
104 111
105 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video 1', fixture: 'video_short.webm' }) 112 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video 1', fixture: 'video_short.webm' })
106 videoUUID = res.body.video.uuid 113 videoUUID = res.body.video.uuid
107 114
108 await waitJobs(servers) 115 await waitJobs(servers)
@@ -113,7 +120,7 @@ describe('Test HLS videos', function () {
113 it('Should upload an audio file and transcode it to HLS', async function () { 120 it('Should upload an audio file and transcode it to HLS', async function () {
114 this.timeout(120000) 121 this.timeout(120000)
115 122
116 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video audio', fixture: 'sample.ogg' }) 123 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video audio', fixture: 'sample.ogg' })
117 videoAudioUUID = res.body.video.uuid 124 videoAudioUUID = res.body.video.uuid
118 125
119 await waitJobs(servers) 126 await waitJobs(servers)
@@ -124,7 +131,7 @@ describe('Test HLS videos', function () {
124 it('Should update the video', async function () { 131 it('Should update the video', async function () {
125 this.timeout(10000) 132 this.timeout(10000)
126 133
127 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, { name: 'video 1 updated' }) 134 await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video 1 updated' })
128 135
129 await waitJobs(servers) 136 await waitJobs(servers)
130 137
@@ -134,8 +141,8 @@ describe('Test HLS videos', function () {
134 it('Should delete videos', async function () { 141 it('Should delete videos', async function () {
135 this.timeout(10000) 142 this.timeout(10000)
136 143
137 await removeVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID) 144 await removeVideo(servers[0].url, servers[0].accessToken, videoUUID)
138 await removeVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAudioUUID) 145 await removeVideo(servers[0].url, servers[0].accessToken, videoAudioUUID)
139 146
140 await waitJobs(servers) 147 await waitJobs(servers)
141 148
diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts
index 1233ed6eb..a67e528c6 100644
--- a/server/tests/api/videos/video-imports.ts
+++ b/server/tests/api/videos/video-imports.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -12,12 +12,11 @@ import {
12 getVideo, 12 getVideo,
13 getVideosList, 13 getVideosList,
14 immutableAssign, 14 immutableAssign,
15 killallServers,
16 ServerInfo, 15 ServerInfo,
17 setAccessTokensToServers 16 setAccessTokensToServers
18} from '../../../../shared/extra-utils' 17} from '../../../../shared/extra-utils'
19import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 18import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
20import { getMagnetURI, getYoutubeVideoUrl, importVideo, getMyVideoImports } from '../../../../shared/extra-utils/videos/video-imports' 19import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports'
21 20
22const expect = chai.expect 21const expect = chai.expect
23 22
@@ -88,12 +87,12 @@ describe('Test video imports', function () {
88 87
89 { 88 {
90 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) 89 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
91 channelIdServer1 = res.body.videoChannels[ 0 ].id 90 channelIdServer1 = res.body.videoChannels[0].id
92 } 91 }
93 92
94 { 93 {
95 const res = await getMyUserInformation(servers[1].url, servers[1].accessToken) 94 const res = await getMyUserInformation(servers[1].url, servers[1].accessToken)
96 channelIdServer2 = res.body.videoChannels[ 0 ].id 95 channelIdServer2 = res.body.videoChannels[0].id
97 } 96 }
98 97
99 await doubleFollow(servers[0], servers[1]) 98 await doubleFollow(servers[0], servers[1])
@@ -214,7 +213,7 @@ describe('Test video imports', function () {
214 213
215 await checkVideoServer2(server.url, res.body.data[0].uuid) 214 await checkVideoServer2(server.url, res.body.data[0].uuid)
216 215
217 const [ ,videoHttp, videoMagnet, videoTorrent ] = res.body.data 216 const [ , videoHttp, videoMagnet, videoTorrent ] = res.body.data
218 await checkVideosServer1(server.url, videoHttp.uuid, videoMagnet.uuid, videoTorrent.uuid) 217 await checkVideosServer1(server.url, videoHttp.uuid, videoMagnet.uuid, videoTorrent.uuid)
219 } 218 }
220 }) 219 })
diff --git a/server/tests/api/videos/video-nsfw.ts b/server/tests/api/videos/video-nsfw.ts
index ad6a4b43f..7eba8d7d9 100644
--- a/server/tests/api/videos/video-nsfw.ts
+++ b/server/tests/api/videos/video-nsfw.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -89,8 +89,8 @@ describe('Test video NSFW policy', function () {
89 89
90 const videos = res.body.data 90 const videos = res.body.data
91 expect(videos).to.have.lengthOf(2) 91 expect(videos).to.have.lengthOf(2)
92 expect(videos[ 0 ].name).to.equal('normal') 92 expect(videos[0].name).to.equal('normal')
93 expect(videos[ 1 ].name).to.equal('nsfw') 93 expect(videos[1].name).to.equal('nsfw')
94 } 94 }
95 }) 95 })
96 96
@@ -107,7 +107,7 @@ describe('Test video NSFW policy', function () {
107 107
108 const videos = res.body.data 108 const videos = res.body.data
109 expect(videos).to.have.lengthOf(1) 109 expect(videos).to.have.lengthOf(1)
110 expect(videos[ 0 ].name).to.equal('normal') 110 expect(videos[0].name).to.equal('normal')
111 } 111 }
112 }) 112 })
113 113
@@ -124,8 +124,8 @@ describe('Test video NSFW policy', function () {
124 124
125 const videos = res.body.data 125 const videos = res.body.data
126 expect(videos).to.have.lengthOf(2) 126 expect(videos).to.have.lengthOf(2)
127 expect(videos[ 0 ].name).to.equal('normal') 127 expect(videos[0].name).to.equal('normal')
128 expect(videos[ 1 ].name).to.equal('nsfw') 128 expect(videos[1].name).to.equal('nsfw')
129 } 129 }
130 }) 130 })
131 }) 131 })
@@ -154,8 +154,8 @@ describe('Test video NSFW policy', function () {
154 154
155 const videos = res.body.data 155 const videos = res.body.data
156 expect(videos).to.have.lengthOf(2) 156 expect(videos).to.have.lengthOf(2)
157 expect(videos[ 0 ].name).to.equal('normal') 157 expect(videos[0].name).to.equal('normal')
158 expect(videos[ 1 ].name).to.equal('nsfw') 158 expect(videos[1].name).to.equal('nsfw')
159 } 159 }
160 }) 160 })
161 161
@@ -171,8 +171,8 @@ describe('Test video NSFW policy', function () {
171 171
172 const videos = res.body.data 172 const videos = res.body.data
173 expect(videos).to.have.lengthOf(2) 173 expect(videos).to.have.lengthOf(2)
174 expect(videos[ 0 ].name).to.equal('normal') 174 expect(videos[0].name).to.equal('normal')
175 expect(videos[ 1 ].name).to.equal('nsfw') 175 expect(videos[1].name).to.equal('nsfw')
176 } 176 }
177 }) 177 })
178 178
@@ -188,7 +188,7 @@ describe('Test video NSFW policy', function () {
188 188
189 const videos = res.body.data 189 const videos = res.body.data
190 expect(videos).to.have.lengthOf(1) 190 expect(videos).to.have.lengthOf(1)
191 expect(videos[ 0 ].name).to.equal('normal') 191 expect(videos[0].name).to.equal('normal')
192 } 192 }
193 }) 193 })
194 194
@@ -198,8 +198,8 @@ describe('Test video NSFW policy', function () {
198 198
199 const videos = res.body.data 199 const videos = res.body.data
200 expect(videos).to.have.lengthOf(2) 200 expect(videos).to.have.lengthOf(2)
201 expect(videos[ 0 ].name).to.equal('normal') 201 expect(videos[0].name).to.equal('normal')
202 expect(videos[ 1 ].name).to.equal('nsfw') 202 expect(videos[1].name).to.equal('nsfw')
203 }) 203 })
204 204
205 it('Should display NSFW videos when the nsfw param === true', async function () { 205 it('Should display NSFW videos when the nsfw param === true', async function () {
@@ -208,7 +208,7 @@ describe('Test video NSFW policy', function () {
208 208
209 const videos = res.body.data 209 const videos = res.body.data
210 expect(videos).to.have.lengthOf(1) 210 expect(videos).to.have.lengthOf(1)
211 expect(videos[ 0 ].name).to.equal('nsfw') 211 expect(videos[0].name).to.equal('nsfw')
212 } 212 }
213 }) 213 })
214 214
@@ -218,7 +218,7 @@ describe('Test video NSFW policy', function () {
218 218
219 const videos = res.body.data 219 const videos = res.body.data
220 expect(videos).to.have.lengthOf(1) 220 expect(videos).to.have.lengthOf(1)
221 expect(videos[ 0 ].name).to.equal('normal') 221 expect(videos[0].name).to.equal('normal')
222 } 222 }
223 }) 223 })
224 224
@@ -228,8 +228,8 @@ describe('Test video NSFW policy', function () {
228 228
229 const videos = res.body.data 229 const videos = res.body.data
230 expect(videos).to.have.lengthOf(2) 230 expect(videos).to.have.lengthOf(2)
231 expect(videos[ 0 ].name).to.equal('normal') 231 expect(videos[0].name).to.equal('normal')
232 expect(videos[ 1 ].name).to.equal('nsfw') 232 expect(videos[1].name).to.equal('nsfw')
233 } 233 }
234 }) 234 })
235 }) 235 })
diff --git a/server/tests/api/videos/video-playlist-thumbnails.ts b/server/tests/api/videos/video-playlist-thumbnails.ts
index 73ab02c17..a93a0b7de 100644
--- a/server/tests/api/videos/video-playlist-thumbnails.ts
+++ b/server/tests/api/videos/video-playlist-thumbnails.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -8,14 +8,15 @@ import {
8 createVideoPlaylist, 8 createVideoPlaylist,
9 doubleFollow, 9 doubleFollow,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
11 getVideoPlaylistsList, removeVideoFromPlaylist, 11 getVideoPlaylistsList,
12 removeVideoFromPlaylist,
13 reorderVideosPlaylist,
12 ServerInfo, 14 ServerInfo,
13 setAccessTokensToServers, 15 setAccessTokensToServers,
14 setDefaultVideoChannel, 16 setDefaultVideoChannel,
15 testImage, 17 testImage,
16 uploadVideoAndGetId, 18 uploadVideoAndGetId,
17 waitJobs, 19 waitJobs
18 reorderVideosPlaylist
19} from '../../../../shared/extra-utils' 20} from '../../../../shared/extra-utils'
20import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' 21import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
21 22
@@ -69,19 +70,19 @@ describe('Playlist thumbnail', function () {
69 this.timeout(30000) 70 this.timeout(30000)
70 71
71 const res = await createVideoPlaylist({ 72 const res = await createVideoPlaylist({
72 url: servers[ 1 ].url, 73 url: servers[1].url,
73 token: servers[ 1 ].accessToken, 74 token: servers[1].accessToken,
74 playlistAttrs: { 75 playlistAttrs: {
75 displayName: 'playlist without thumbnail', 76 displayName: 'playlist without thumbnail',
76 privacy: VideoPlaylistPrivacy.PUBLIC, 77 privacy: VideoPlaylistPrivacy.PUBLIC,
77 videoChannelId: servers[ 1 ].videoChannel.id 78 videoChannelId: servers[1].videoChannel.id
78 } 79 }
79 }) 80 })
80 playlistWithoutThumbnail = res.body.videoPlaylist.id 81 playlistWithoutThumbnail = res.body.videoPlaylist.id
81 82
82 const res2 = await addVideoInPlaylist({ 83 const res2 = await addVideoInPlaylist({
83 url: servers[ 1 ].url, 84 url: servers[1].url,
84 token: servers[ 1 ].accessToken, 85 token: servers[1].accessToken,
85 playlistId: playlistWithoutThumbnail, 86 playlistId: playlistWithoutThumbnail,
86 elementAttrs: { videoId: video1 } 87 elementAttrs: { videoId: video1 }
87 }) 88 })
@@ -99,20 +100,20 @@ describe('Playlist thumbnail', function () {
99 this.timeout(30000) 100 this.timeout(30000)
100 101
101 const res = await createVideoPlaylist({ 102 const res = await createVideoPlaylist({
102 url: servers[ 1 ].url, 103 url: servers[1].url,
103 token: servers[ 1 ].accessToken, 104 token: servers[1].accessToken,
104 playlistAttrs: { 105 playlistAttrs: {
105 displayName: 'playlist with thumbnail', 106 displayName: 'playlist with thumbnail',
106 privacy: VideoPlaylistPrivacy.PUBLIC, 107 privacy: VideoPlaylistPrivacy.PUBLIC,
107 videoChannelId: servers[ 1 ].videoChannel.id, 108 videoChannelId: servers[1].videoChannel.id,
108 thumbnailfile: 'thumbnail.jpg' 109 thumbnailfile: 'thumbnail.jpg'
109 } 110 }
110 }) 111 })
111 playlistWithThumbnail = res.body.videoPlaylist.id 112 playlistWithThumbnail = res.body.videoPlaylist.id
112 113
113 const res2 = await addVideoInPlaylist({ 114 const res2 = await addVideoInPlaylist({
114 url: servers[ 1 ].url, 115 url: servers[1].url,
115 token: servers[ 1 ].accessToken, 116 token: servers[1].accessToken,
116 playlistId: playlistWithThumbnail, 117 playlistId: playlistWithThumbnail,
117 elementAttrs: { videoId: video1 } 118 elementAttrs: { videoId: video1 }
118 }) 119 })
@@ -130,8 +131,8 @@ describe('Playlist thumbnail', function () {
130 this.timeout(30000) 131 this.timeout(30000)
131 132
132 const res = await addVideoInPlaylist({ 133 const res = await addVideoInPlaylist({
133 url: servers[ 1 ].url, 134 url: servers[1].url,
134 token: servers[ 1 ].accessToken, 135 token: servers[1].accessToken,
135 playlistId: playlistWithoutThumbnail, 136 playlistId: playlistWithoutThumbnail,
136 elementAttrs: { videoId: video2 } 137 elementAttrs: { videoId: video2 }
137 }) 138 })
@@ -159,8 +160,8 @@ describe('Playlist thumbnail', function () {
159 this.timeout(30000) 160 this.timeout(30000)
160 161
161 const res = await addVideoInPlaylist({ 162 const res = await addVideoInPlaylist({
162 url: servers[ 1 ].url, 163 url: servers[1].url,
163 token: servers[ 1 ].accessToken, 164 token: servers[1].accessToken,
164 playlistId: playlistWithThumbnail, 165 playlistId: playlistWithThumbnail,
165 elementAttrs: { videoId: video2 } 166 elementAttrs: { videoId: video2 }
166 }) 167 })
@@ -188,8 +189,8 @@ describe('Playlist thumbnail', function () {
188 this.timeout(30000) 189 this.timeout(30000)
189 190
190 await removeVideoFromPlaylist({ 191 await removeVideoFromPlaylist({
191 url: servers[ 1 ].url, 192 url: servers[1].url,
192 token: servers[ 1 ].accessToken, 193 token: servers[1].accessToken,
193 playlistId: playlistWithoutThumbnail, 194 playlistId: playlistWithoutThumbnail,
194 playlistElementId: withoutThumbnailE1 195 playlistElementId: withoutThumbnailE1
195 }) 196 })
@@ -206,8 +207,8 @@ describe('Playlist thumbnail', function () {
206 this.timeout(30000) 207 this.timeout(30000)
207 208
208 await removeVideoFromPlaylist({ 209 await removeVideoFromPlaylist({
209 url: servers[ 1 ].url, 210 url: servers[1].url,
210 token: servers[ 1 ].accessToken, 211 token: servers[1].accessToken,
211 playlistId: playlistWithThumbnail, 212 playlistId: playlistWithThumbnail,
212 playlistElementId: withThumbnailE1 213 playlistElementId: withThumbnailE1
213 }) 214 })
@@ -224,8 +225,8 @@ describe('Playlist thumbnail', function () {
224 this.timeout(30000) 225 this.timeout(30000)
225 226
226 await removeVideoFromPlaylist({ 227 await removeVideoFromPlaylist({
227 url: servers[ 1 ].url, 228 url: servers[1].url,
228 token: servers[ 1 ].accessToken, 229 token: servers[1].accessToken,
229 playlistId: playlistWithoutThumbnail, 230 playlistId: playlistWithoutThumbnail,
230 playlistElementId: withoutThumbnailE2 231 playlistElementId: withoutThumbnailE2
231 }) 232 })
@@ -242,8 +243,8 @@ describe('Playlist thumbnail', function () {
242 this.timeout(30000) 243 this.timeout(30000)
243 244
244 await removeVideoFromPlaylist({ 245 await removeVideoFromPlaylist({
245 url: servers[ 1 ].url, 246 url: servers[1].url,
246 token: servers[ 1 ].accessToken, 247 token: servers[1].accessToken,
247 playlistId: playlistWithThumbnail, 248 playlistId: playlistWithThumbnail,
248 playlistElementId: withThumbnailE2 249 playlistElementId: withThumbnailE2
249 }) 250 })
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts
index 9fd48ac7c..2bb97d7a8 100644
--- a/server/tests/api/videos/video-playlists.ts
+++ b/server/tests/api/videos/video-playlists.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -141,12 +141,12 @@ describe('Test video playlists', function () {
141 servers[2].videos = await Promise.all(serverPromises[2]) 141 servers[2].videos = await Promise.all(serverPromises[2])
142 } 142 }
143 143
144 nsfwVideoServer1 = (await uploadVideoAndGetId({ server: servers[ 0 ], videoName: 'NSFW video', nsfw: true })).id 144 nsfwVideoServer1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'NSFW video', nsfw: true })).id
145 145
146 { 146 {
147 await createUser({ 147 await createUser({
148 url: servers[ 0 ].url, 148 url: servers[0].url,
149 accessToken: servers[ 0 ].accessToken, 149 accessToken: servers[0].accessToken,
150 username: 'user1', 150 username: 'user1',
151 password: 'password' 151 password: 'password'
152 }) 152 })
@@ -158,17 +158,17 @@ describe('Test video playlists', function () {
158 158
159 describe('Get default playlists', function () { 159 describe('Get default playlists', function () {
160 it('Should list video playlist privacies', async function () { 160 it('Should list video playlist privacies', async function () {
161 const res = await getVideoPlaylistPrivacies(servers[ 0 ].url) 161 const res = await getVideoPlaylistPrivacies(servers[0].url)
162 162
163 const privacies = res.body 163 const privacies = res.body
164 expect(Object.keys(privacies)).to.have.length.at.least(3) 164 expect(Object.keys(privacies)).to.have.length.at.least(3)
165 165
166 expect(privacies[ 3 ]).to.equal('Private') 166 expect(privacies[3]).to.equal('Private')
167 }) 167 })
168 168
169 it('Should list watch later playlist', async function () { 169 it('Should list watch later playlist', async function () {
170 const url = servers[ 0 ].url 170 const url = servers[0].url
171 const accessToken = servers[ 0 ].accessToken 171 const accessToken = servers[0].accessToken
172 172
173 { 173 {
174 const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.WATCH_LATER) 174 const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.WATCH_LATER)
@@ -176,7 +176,7 @@ describe('Test video playlists', function () {
176 expect(res.body.total).to.equal(1) 176 expect(res.body.total).to.equal(1)
177 expect(res.body.data).to.have.lengthOf(1) 177 expect(res.body.data).to.have.lengthOf(1)
178 178
179 const playlist: VideoPlaylist = res.body.data[ 0 ] 179 const playlist: VideoPlaylist = res.body.data[0]
180 expect(playlist.displayName).to.equal('Watch later') 180 expect(playlist.displayName).to.equal('Watch later')
181 expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER) 181 expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER)
182 expect(playlist.type.label).to.equal('Watch later') 182 expect(playlist.type.label).to.equal('Watch later')
@@ -197,15 +197,15 @@ describe('Test video playlists', function () {
197 }) 197 })
198 198
199 it('Should get private playlist for a classic user', async function () { 199 it('Should get private playlist for a classic user', async function () {
200 const token = await generateUserAccessToken(servers[ 0 ], 'toto') 200 const token = await generateUserAccessToken(servers[0], 'toto')
201 201
202 const res = await getAccountPlaylistsListWithToken(servers[ 0 ].url, token, 'toto', 0, 5) 202 const res = await getAccountPlaylistsListWithToken(servers[0].url, token, 'toto', 0, 5)
203 203
204 expect(res.body.total).to.equal(1) 204 expect(res.body.total).to.equal(1)
205 expect(res.body.data).to.have.lengthOf(1) 205 expect(res.body.data).to.have.lengthOf(1)
206 206
207 const playlistId = res.body.data[ 0 ].id 207 const playlistId = res.body.data[0].id
208 await getPlaylistVideos(servers[ 0 ].url, token, playlistId, 0, 5) 208 await getPlaylistVideos(servers[0].url, token, playlistId, 0, 5)
209 }) 209 })
210 }) 210 })
211 211
@@ -215,14 +215,14 @@ describe('Test video playlists', function () {
215 this.timeout(30000) 215 this.timeout(30000)
216 216
217 await createVideoPlaylist({ 217 await createVideoPlaylist({
218 url: servers[ 0 ].url, 218 url: servers[0].url,
219 token: servers[ 0 ].accessToken, 219 token: servers[0].accessToken,
220 playlistAttrs: { 220 playlistAttrs: {
221 displayName: 'my super playlist', 221 displayName: 'my super playlist',
222 privacy: VideoPlaylistPrivacy.PUBLIC, 222 privacy: VideoPlaylistPrivacy.PUBLIC,
223 description: 'my super description', 223 description: 'my super description',
224 thumbnailfile: 'thumbnail.jpg', 224 thumbnailfile: 'thumbnail.jpg',
225 videoChannelId: servers[ 0 ].videoChannel.id 225 videoChannelId: servers[0].videoChannel.id
226 } 226 }
227 }) 227 })
228 228
@@ -233,7 +233,7 @@ describe('Test video playlists', function () {
233 expect(res.body.total).to.equal(1) 233 expect(res.body.total).to.equal(1)
234 expect(res.body.data).to.have.lengthOf(1) 234 expect(res.body.data).to.have.lengthOf(1)
235 235
236 const playlistFromList = res.body.data[ 0 ] as VideoPlaylist 236 const playlistFromList = res.body.data[0] as VideoPlaylist
237 237
238 const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid) 238 const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid)
239 const playlistFromGet = res2.body 239 const playlistFromGet = res2.body
@@ -266,12 +266,12 @@ describe('Test video playlists', function () {
266 266
267 { 267 {
268 const res = await createVideoPlaylist({ 268 const res = await createVideoPlaylist({
269 url: servers[ 1 ].url, 269 url: servers[1].url,
270 token: servers[ 1 ].accessToken, 270 token: servers[1].accessToken,
271 playlistAttrs: { 271 playlistAttrs: {
272 displayName: 'playlist 2', 272 displayName: 'playlist 2',
273 privacy: VideoPlaylistPrivacy.PUBLIC, 273 privacy: VideoPlaylistPrivacy.PUBLIC,
274 videoChannelId: servers[ 1 ].videoChannel.id 274 videoChannelId: servers[1].videoChannel.id
275 } 275 }
276 }) 276 })
277 playlistServer2Id1 = res.body.videoPlaylist.id 277 playlistServer2Id1 = res.body.videoPlaylist.id
@@ -279,13 +279,13 @@ describe('Test video playlists', function () {
279 279
280 { 280 {
281 const res = await createVideoPlaylist({ 281 const res = await createVideoPlaylist({
282 url: servers[ 1 ].url, 282 url: servers[1].url,
283 token: servers[ 1 ].accessToken, 283 token: servers[1].accessToken,
284 playlistAttrs: { 284 playlistAttrs: {
285 displayName: 'playlist 3', 285 displayName: 'playlist 3',
286 privacy: VideoPlaylistPrivacy.PUBLIC, 286 privacy: VideoPlaylistPrivacy.PUBLIC,
287 thumbnailfile: 'thumbnail.jpg', 287 thumbnailfile: 'thumbnail.jpg',
288 videoChannelId: servers[ 1 ].videoChannel.id 288 videoChannelId: servers[1].videoChannel.id
289 } 289 }
290 }) 290 })
291 291
@@ -293,24 +293,24 @@ describe('Test video playlists', function () {
293 playlistServer2UUID2 = res.body.videoPlaylist.uuid 293 playlistServer2UUID2 = res.body.videoPlaylist.uuid
294 } 294 }
295 295
296 for (let id of [ playlistServer2Id1, playlistServer2Id2 ]) { 296 for (const id of [ playlistServer2Id1, playlistServer2Id2 ]) {
297 await addVideoInPlaylist({ 297 await addVideoInPlaylist({
298 url: servers[ 1 ].url, 298 url: servers[1].url,
299 token: servers[ 1 ].accessToken, 299 token: servers[1].accessToken,
300 playlistId: id, 300 playlistId: id,
301 elementAttrs: { videoId: servers[ 1 ].videos[ 0 ].id, startTimestamp: 1, stopTimestamp: 2 } 301 elementAttrs: { videoId: servers[1].videos[0].id, startTimestamp: 1, stopTimestamp: 2 }
302 }) 302 })
303 await addVideoInPlaylist({ 303 await addVideoInPlaylist({
304 url: servers[ 1 ].url, 304 url: servers[1].url,
305 token: servers[ 1 ].accessToken, 305 token: servers[1].accessToken,
306 playlistId: id, 306 playlistId: id,
307 elementAttrs: { videoId: servers[ 1 ].videos[ 1 ].id } 307 elementAttrs: { videoId: servers[1].videos[1].id }
308 }) 308 })
309 } 309 }
310 310
311 await waitJobs(servers) 311 await waitJobs(servers)
312 312
313 for (const server of [ servers[ 0 ], servers[ 1 ] ]) { 313 for (const server of [ servers[0], servers[1] ]) {
314 const res = await getVideoPlaylistsList(server.url, 0, 5) 314 const res = await getVideoPlaylistsList(server.url, 0, 5)
315 315
316 const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') 316 const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2')
@@ -322,7 +322,7 @@ describe('Test video playlists', function () {
322 await testImage(server.url, 'thumbnail', playlist3.thumbnailPath) 322 await testImage(server.url, 'thumbnail', playlist3.thumbnailPath)
323 } 323 }
324 324
325 const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) 325 const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
326 expect(res.body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined 326 expect(res.body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined
327 expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined 327 expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined
328 }) 328 })
@@ -331,13 +331,13 @@ describe('Test video playlists', function () {
331 this.timeout(30000) 331 this.timeout(30000)
332 332
333 // Server 2 and server 3 follow each other 333 // Server 2 and server 3 follow each other
334 await doubleFollow(servers[ 1 ], servers[ 2 ]) 334 await doubleFollow(servers[1], servers[2])
335 335
336 const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) 336 const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
337 337
338 const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') 338 const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2')
339 expect(playlist2).to.not.be.undefined 339 expect(playlist2).to.not.be.undefined
340 await testImage(servers[ 2 ].url, 'thumbnail-playlist', playlist2.thumbnailPath) 340 await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath)
341 341
342 expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined 342 expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined
343 }) 343 })
@@ -349,25 +349,25 @@ describe('Test video playlists', function () {
349 this.timeout(30000) 349 this.timeout(30000)
350 350
351 { 351 {
352 const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, 'createdAt') 352 const res = await getVideoPlaylistsList(servers[2].url, 1, 2, 'createdAt')
353 353
354 expect(res.body.total).to.equal(3) 354 expect(res.body.total).to.equal(3)
355 355
356 const data: VideoPlaylist[] = res.body.data 356 const data: VideoPlaylist[] = res.body.data
357 expect(data).to.have.lengthOf(2) 357 expect(data).to.have.lengthOf(2)
358 expect(data[ 0 ].displayName).to.equal('playlist 2') 358 expect(data[0].displayName).to.equal('playlist 2')
359 expect(data[ 1 ].displayName).to.equal('playlist 3') 359 expect(data[1].displayName).to.equal('playlist 3')
360 } 360 }
361 361
362 { 362 {
363 const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, '-createdAt') 363 const res = await getVideoPlaylistsList(servers[2].url, 1, 2, '-createdAt')
364 364
365 expect(res.body.total).to.equal(3) 365 expect(res.body.total).to.equal(3)
366 366
367 const data: VideoPlaylist[] = res.body.data 367 const data: VideoPlaylist[] = res.body.data
368 expect(data).to.have.lengthOf(2) 368 expect(data).to.have.lengthOf(2)
369 expect(data[ 0 ].displayName).to.equal('playlist 2') 369 expect(data[0].displayName).to.equal('playlist 2')
370 expect(data[ 1 ].displayName).to.equal('my super playlist') 370 expect(data[1].displayName).to.equal('my super playlist')
371 } 371 }
372 }) 372 })
373 373
@@ -375,13 +375,13 @@ describe('Test video playlists', function () {
375 this.timeout(30000) 375 this.timeout(30000)
376 376
377 { 377 {
378 const res = await getVideoChannelPlaylistsList(servers[ 0 ].url, 'root_channel', 0, 2, '-createdAt') 378 const res = await getVideoChannelPlaylistsList(servers[0].url, 'root_channel', 0, 2, '-createdAt')
379 379
380 expect(res.body.total).to.equal(1) 380 expect(res.body.total).to.equal(1)
381 381
382 const data: VideoPlaylist[] = res.body.data 382 const data: VideoPlaylist[] = res.body.data
383 expect(data).to.have.lengthOf(1) 383 expect(data).to.have.lengthOf(1)
384 expect(data[ 0 ].displayName).to.equal('my super playlist') 384 expect(data[0].displayName).to.equal('my super playlist')
385 } 385 }
386 }) 386 })
387 387
@@ -389,37 +389,37 @@ describe('Test video playlists', function () {
389 this.timeout(30000) 389 this.timeout(30000)
390 390
391 { 391 {
392 const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, '-createdAt') 392 const res = await getAccountPlaylistsList(servers[1].url, 'root', 1, 2, '-createdAt')
393 393
394 expect(res.body.total).to.equal(2) 394 expect(res.body.total).to.equal(2)
395 395
396 const data: VideoPlaylist[] = res.body.data 396 const data: VideoPlaylist[] = res.body.data
397 expect(data).to.have.lengthOf(1) 397 expect(data).to.have.lengthOf(1)
398 expect(data[ 0 ].displayName).to.equal('playlist 2') 398 expect(data[0].displayName).to.equal('playlist 2')
399 } 399 }
400 400
401 { 401 {
402 const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, 'createdAt') 402 const res = await getAccountPlaylistsList(servers[1].url, 'root', 1, 2, 'createdAt')
403 403
404 expect(res.body.total).to.equal(2) 404 expect(res.body.total).to.equal(2)
405 405
406 const data: VideoPlaylist[] = res.body.data 406 const data: VideoPlaylist[] = res.body.data
407 expect(data).to.have.lengthOf(1) 407 expect(data).to.have.lengthOf(1)
408 expect(data[ 0 ].displayName).to.equal('playlist 3') 408 expect(data[0].displayName).to.equal('playlist 3')
409 } 409 }
410 410
411 { 411 {
412 const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 0, 10, 'createdAt', '3') 412 const res = await getAccountPlaylistsList(servers[1].url, 'root', 0, 10, 'createdAt', '3')
413 413
414 expect(res.body.total).to.equal(1) 414 expect(res.body.total).to.equal(1)
415 415
416 const data: VideoPlaylist[] = res.body.data 416 const data: VideoPlaylist[] = res.body.data
417 expect(data).to.have.lengthOf(1) 417 expect(data).to.have.lengthOf(1)
418 expect(data[ 0 ].displayName).to.equal('playlist 3') 418 expect(data[0].displayName).to.equal('playlist 3')
419 } 419 }
420 420
421 { 421 {
422 const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 0, 10, 'createdAt', '4') 422 const res = await getAccountPlaylistsList(servers[1].url, 'root', 0, 10, 'createdAt', '4')
423 423
424 expect(res.body.total).to.equal(0) 424 expect(res.body.total).to.equal(0)
425 425
@@ -432,8 +432,8 @@ describe('Test video playlists', function () {
432 this.timeout(30000) 432 this.timeout(30000)
433 433
434 await createVideoPlaylist({ 434 await createVideoPlaylist({
435 url: servers[ 1 ].url, 435 url: servers[1].url,
436 token: servers[ 1 ].accessToken, 436 token: servers[1].accessToken,
437 playlistAttrs: { 437 playlistAttrs: {
438 displayName: 'playlist unlisted', 438 displayName: 'playlist unlisted',
439 privacy: VideoPlaylistPrivacy.UNLISTED 439 privacy: VideoPlaylistPrivacy.UNLISTED
@@ -441,8 +441,8 @@ describe('Test video playlists', function () {
441 }) 441 })
442 442
443 await createVideoPlaylist({ 443 await createVideoPlaylist({
444 url: servers[ 1 ].url, 444 url: servers[1].url,
445 token: servers[ 1 ].accessToken, 445 token: servers[1].accessToken,
446 playlistAttrs: { 446 playlistAttrs: {
447 displayName: 'playlist private', 447 displayName: 'playlist private',
448 privacy: VideoPlaylistPrivacy.PRIVATE 448 privacy: VideoPlaylistPrivacy.PRIVATE
@@ -453,18 +453,18 @@ describe('Test video playlists', function () {
453 453
454 for (const server of servers) { 454 for (const server of servers) {
455 const results = [ 455 const results = [
456 await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[ 1 ].port, 0, 5, '-createdAt'), 456 await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[1].port, 0, 5, '-createdAt'),
457 await getVideoPlaylistsList(server.url, 0, 2, '-createdAt') 457 await getVideoPlaylistsList(server.url, 0, 2, '-createdAt')
458 ] 458 ]
459 459
460 expect(results[ 0 ].body.total).to.equal(2) 460 expect(results[0].body.total).to.equal(2)
461 expect(results[ 1 ].body.total).to.equal(3) 461 expect(results[1].body.total).to.equal(3)
462 462
463 for (const res of results) { 463 for (const res of results) {
464 const data: VideoPlaylist[] = res.body.data 464 const data: VideoPlaylist[] = res.body.data
465 expect(data).to.have.lengthOf(2) 465 expect(data).to.have.lengthOf(2)
466 expect(data[ 0 ].displayName).to.equal('playlist 3') 466 expect(data[0].displayName).to.equal('playlist 3')
467 expect(data[ 1 ].displayName).to.equal('playlist 2') 467 expect(data[1].displayName).to.equal('playlist 2')
468 } 468 }
469 } 469 }
470 }) 470 })
@@ -519,32 +519,32 @@ describe('Test video playlists', function () {
519 this.timeout(30000) 519 this.timeout(30000)
520 520
521 const addVideo = (elementAttrs: any) => { 521 const addVideo = (elementAttrs: any) => {
522 return addVideoInPlaylist({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, playlistId: playlistServer1Id, elementAttrs }) 522 return addVideoInPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: playlistServer1Id, elementAttrs })
523 } 523 }
524 524
525 const res = await createVideoPlaylist({ 525 const res = await createVideoPlaylist({
526 url: servers[ 0 ].url, 526 url: servers[0].url,
527 token: servers[ 0 ].accessToken, 527 token: servers[0].accessToken,
528 playlistAttrs: { 528 playlistAttrs: {
529 displayName: 'playlist 4', 529 displayName: 'playlist 4',
530 privacy: VideoPlaylistPrivacy.PUBLIC, 530 privacy: VideoPlaylistPrivacy.PUBLIC,
531 videoChannelId: servers[ 0 ].videoChannel.id 531 videoChannelId: servers[0].videoChannel.id
532 } 532 }
533 }) 533 })
534 534
535 playlistServer1Id = res.body.videoPlaylist.id 535 playlistServer1Id = res.body.videoPlaylist.id
536 playlistServer1UUID = res.body.videoPlaylist.uuid 536 playlistServer1UUID = res.body.videoPlaylist.uuid
537 537
538 await addVideo({ videoId: servers[ 0 ].videos[ 0 ].uuid, startTimestamp: 15, stopTimestamp: 28 }) 538 await addVideo({ videoId: servers[0].videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
539 await addVideo({ videoId: servers[ 2 ].videos[ 1 ].uuid, startTimestamp: 35 }) 539 await addVideo({ videoId: servers[2].videos[1].uuid, startTimestamp: 35 })
540 await addVideo({ videoId: servers[ 2 ].videos[ 2 ].uuid }) 540 await addVideo({ videoId: servers[2].videos[2].uuid })
541 { 541 {
542 const res = await addVideo({ videoId: servers[ 0 ].videos[ 3 ].uuid, stopTimestamp: 35 }) 542 const res = await addVideo({ videoId: servers[0].videos[3].uuid, stopTimestamp: 35 })
543 playlistElementServer1Video4 = res.body.videoPlaylistElement.id 543 playlistElementServer1Video4 = res.body.videoPlaylistElement.id
544 } 544 }
545 545
546 { 546 {
547 const res = await addVideo({ videoId: servers[ 0 ].videos[ 4 ].uuid, startTimestamp: 45, stopTimestamp: 60 }) 547 const res = await addVideo({ videoId: servers[0].videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 })
548 playlistElementServer1Video5 = res.body.videoPlaylistElement.id 548 playlistElementServer1Video5 = res.body.videoPlaylistElement.id
549 } 549 }
550 550
@@ -567,35 +567,35 @@ describe('Test video playlists', function () {
567 const videoElements: VideoPlaylistElement[] = res.body.data 567 const videoElements: VideoPlaylistElement[] = res.body.data
568 expect(videoElements).to.have.lengthOf(6) 568 expect(videoElements).to.have.lengthOf(6)
569 569
570 expect(videoElements[ 0 ].video.name).to.equal('video 0 server 1') 570 expect(videoElements[0].video.name).to.equal('video 0 server 1')
571 expect(videoElements[ 0 ].position).to.equal(1) 571 expect(videoElements[0].position).to.equal(1)
572 expect(videoElements[ 0 ].startTimestamp).to.equal(15) 572 expect(videoElements[0].startTimestamp).to.equal(15)
573 expect(videoElements[ 0 ].stopTimestamp).to.equal(28) 573 expect(videoElements[0].stopTimestamp).to.equal(28)
574 574
575 expect(videoElements[ 1 ].video.name).to.equal('video 1 server 3') 575 expect(videoElements[1].video.name).to.equal('video 1 server 3')
576 expect(videoElements[ 1 ].position).to.equal(2) 576 expect(videoElements[1].position).to.equal(2)
577 expect(videoElements[ 1 ].startTimestamp).to.equal(35) 577 expect(videoElements[1].startTimestamp).to.equal(35)
578 expect(videoElements[ 1 ].stopTimestamp).to.be.null 578 expect(videoElements[1].stopTimestamp).to.be.null
579 579
580 expect(videoElements[ 2 ].video.name).to.equal('video 2 server 3') 580 expect(videoElements[2].video.name).to.equal('video 2 server 3')
581 expect(videoElements[ 2 ].position).to.equal(3) 581 expect(videoElements[2].position).to.equal(3)
582 expect(videoElements[ 2 ].startTimestamp).to.be.null 582 expect(videoElements[2].startTimestamp).to.be.null
583 expect(videoElements[ 2 ].stopTimestamp).to.be.null 583 expect(videoElements[2].stopTimestamp).to.be.null
584 584
585 expect(videoElements[ 3 ].video.name).to.equal('video 3 server 1') 585 expect(videoElements[3].video.name).to.equal('video 3 server 1')
586 expect(videoElements[ 3 ].position).to.equal(4) 586 expect(videoElements[3].position).to.equal(4)
587 expect(videoElements[ 3 ].startTimestamp).to.be.null 587 expect(videoElements[3].startTimestamp).to.be.null
588 expect(videoElements[ 3 ].stopTimestamp).to.equal(35) 588 expect(videoElements[3].stopTimestamp).to.equal(35)
589 589
590 expect(videoElements[ 4 ].video.name).to.equal('video 4 server 1') 590 expect(videoElements[4].video.name).to.equal('video 4 server 1')
591 expect(videoElements[ 4 ].position).to.equal(5) 591 expect(videoElements[4].position).to.equal(5)
592 expect(videoElements[ 4 ].startTimestamp).to.equal(45) 592 expect(videoElements[4].startTimestamp).to.equal(45)
593 expect(videoElements[ 4 ].stopTimestamp).to.equal(60) 593 expect(videoElements[4].stopTimestamp).to.equal(60)
594 594
595 expect(videoElements[ 5 ].video.name).to.equal('NSFW video') 595 expect(videoElements[5].video.name).to.equal('NSFW video')
596 expect(videoElements[ 5 ].position).to.equal(6) 596 expect(videoElements[5].position).to.equal(6)
597 expect(videoElements[ 5 ].startTimestamp).to.equal(5) 597 expect(videoElements[5].startTimestamp).to.equal(5)
598 expect(videoElements[ 5 ].stopTimestamp).to.be.null 598 expect(videoElements[5].stopTimestamp).to.be.null
599 599
600 const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2) 600 const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2)
601 expect(res3.body.data).to.have.lengthOf(2) 601 expect(res3.body.data).to.have.lengthOf(2)
@@ -616,18 +616,18 @@ describe('Test video playlists', function () {
616 before(async function () { 616 before(async function () {
617 this.timeout(30000) 617 this.timeout(30000)
618 618
619 groupUser1 = [ Object.assign({}, servers[ 0 ], { accessToken: userAccessTokenServer1 }) ] 619 groupUser1 = [ Object.assign({}, servers[0], { accessToken: userAccessTokenServer1 }) ]
620 groupWithoutToken1 = [ Object.assign({}, servers[ 0 ], { accessToken: undefined }) ] 620 groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ]
621 group1 = [ servers[ 0 ] ] 621 group1 = [ servers[0] ]
622 group2 = [ servers[ 1 ], servers[ 2 ] ] 622 group2 = [ servers[1], servers[2] ]
623 623
624 const res = await createVideoPlaylist({ 624 const res = await createVideoPlaylist({
625 url: servers[ 0 ].url, 625 url: servers[0].url,
626 token: userAccessTokenServer1, 626 token: userAccessTokenServer1,
627 playlistAttrs: { 627 playlistAttrs: {
628 displayName: 'playlist 56', 628 displayName: 'playlist 56',
629 privacy: VideoPlaylistPrivacy.PUBLIC, 629 privacy: VideoPlaylistPrivacy.PUBLIC,
630 videoChannelId: servers[ 0 ].videoChannel.id 630 videoChannelId: servers[0].videoChannel.id
631 } 631 }
632 }) 632 })
633 633
@@ -635,7 +635,7 @@ describe('Test video playlists', function () {
635 playlistServer1UUID2 = res.body.videoPlaylist.uuid 635 playlistServer1UUID2 = res.body.videoPlaylist.uuid
636 636
637 const addVideo = (elementAttrs: any) => { 637 const addVideo = (elementAttrs: any) => {
638 return addVideoInPlaylist({ url: servers[ 0 ].url, token: userAccessTokenServer1, playlistId: playlistServer1Id2, elementAttrs }) 638 return addVideoInPlaylist({ url: servers[0].url, token: userAccessTokenServer1, playlistId: playlistServer1Id2, elementAttrs })
639 } 639 }
640 640
641 video1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 89', token: userAccessTokenServer1 })).uuid 641 video1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 89', token: userAccessTokenServer1 })).uuid
@@ -656,7 +656,7 @@ describe('Test video playlists', function () {
656 const position = 1 656 const position = 1
657 657
658 { 658 {
659 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video1, { privacy: VideoPrivacy.PRIVATE }) 659 await updateVideo(servers[0].url, servers[0].accessToken, video1, { privacy: VideoPrivacy.PRIVATE })
660 await waitJobs(servers) 660 await waitJobs(servers)
661 661
662 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 662 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
@@ -666,7 +666,7 @@ describe('Test video playlists', function () {
666 } 666 }
667 667
668 { 668 {
669 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video1, { privacy: VideoPrivacy.PUBLIC }) 669 await updateVideo(servers[0].url, servers[0].accessToken, video1, { privacy: VideoPrivacy.PUBLIC })
670 await waitJobs(servers) 670 await waitJobs(servers)
671 671
672 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 672 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
@@ -684,7 +684,7 @@ describe('Test video playlists', function () {
684 const position = 1 684 const position = 1
685 685
686 { 686 {
687 await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video1, 'reason', true) 687 await addVideoToBlacklist(servers[0].url, servers[0].accessToken, video1, 'reason', true)
688 await waitJobs(servers) 688 await waitJobs(servers)
689 689
690 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 690 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
@@ -694,7 +694,7 @@ describe('Test video playlists', function () {
694 } 694 }
695 695
696 { 696 {
697 await removeVideoFromBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video1) 697 await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, video1)
698 await waitJobs(servers) 698 await waitJobs(servers)
699 699
700 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 700 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
@@ -712,52 +712,52 @@ describe('Test video playlists', function () {
712 const position = 2 712 const position = 2
713 713
714 { 714 {
715 await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port) 715 await addAccountToAccountBlocklist(servers[0].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port)
716 await waitJobs(servers) 716 await waitJobs(servers)
717 717
718 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) 718 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
719 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 719 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
720 720
721 await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port) 721 await removeAccountFromAccountBlocklist(servers[0].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port)
722 await waitJobs(servers) 722 await waitJobs(servers)
723 723
724 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 724 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
725 } 725 }
726 726
727 { 727 {
728 await addServerToAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'localhost:' + servers[1].port) 728 await addServerToAccountBlocklist(servers[0].url, userAccessTokenServer1, 'localhost:' + servers[1].port)
729 await waitJobs(servers) 729 await waitJobs(servers)
730 730
731 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) 731 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
732 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 732 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
733 733
734 await removeServerFromAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'localhost:' + servers[1].port) 734 await removeServerFromAccountBlocklist(servers[0].url, userAccessTokenServer1, 'localhost:' + servers[1].port)
735 await waitJobs(servers) 735 await waitJobs(servers)
736 736
737 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 737 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
738 } 738 }
739 739
740 { 740 {
741 await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'root@localhost:' + servers[1].port) 741 await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'root@localhost:' + servers[1].port)
742 await waitJobs(servers) 742 await waitJobs(servers)
743 743
744 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) 744 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
745 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 745 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
746 746
747 await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'root@localhost:' + servers[1].port) 747 await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, 'root@localhost:' + servers[1].port)
748 await waitJobs(servers) 748 await waitJobs(servers)
749 749
750 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 750 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
751 } 751 }
752 752
753 { 753 {
754 await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) 754 await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
755 await waitJobs(servers) 755 await waitJobs(servers)
756 756
757 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) 757 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
758 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 758 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
759 759
760 await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) 760 await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
761 await waitJobs(servers) 761 await waitJobs(servers)
762 762
763 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 763 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
@@ -785,8 +785,8 @@ describe('Test video playlists', function () {
785 785
786 { 786 {
787 await reorderVideosPlaylist({ 787 await reorderVideosPlaylist({
788 url: servers[ 0 ].url, 788 url: servers[0].url,
789 token: servers[ 0 ].accessToken, 789 token: servers[0].accessToken,
790 playlistId: playlistServer1Id, 790 playlistId: playlistServer1Id,
791 elementAttrs: { 791 elementAttrs: {
792 startPosition: 2, 792 startPosition: 2,
@@ -813,8 +813,8 @@ describe('Test video playlists', function () {
813 813
814 { 814 {
815 await reorderVideosPlaylist({ 815 await reorderVideosPlaylist({
816 url: servers[ 0 ].url, 816 url: servers[0].url,
817 token: servers[ 0 ].accessToken, 817 token: servers[0].accessToken,
818 playlistId: playlistServer1Id, 818 playlistId: playlistServer1Id,
819 elementAttrs: { 819 elementAttrs: {
820 startPosition: 1, 820 startPosition: 1,
@@ -842,8 +842,8 @@ describe('Test video playlists', function () {
842 842
843 { 843 {
844 await reorderVideosPlaylist({ 844 await reorderVideosPlaylist({
845 url: servers[ 0 ].url, 845 url: servers[0].url,
846 token: servers[ 0 ].accessToken, 846 token: servers[0].accessToken,
847 playlistId: playlistServer1Id, 847 playlistId: playlistServer1Id,
848 elementAttrs: { 848 elementAttrs: {
849 startPosition: 6, 849 startPosition: 6,
@@ -868,7 +868,7 @@ describe('Test video playlists', function () {
868 ]) 868 ])
869 869
870 for (let i = 1; i <= elements.length; i++) { 870 for (let i = 1; i <= elements.length; i++) {
871 expect(elements[ i - 1 ].position).to.equal(i) 871 expect(elements[i - 1].position).to.equal(i)
872 } 872 }
873 } 873 }
874 } 874 }
@@ -878,8 +878,8 @@ describe('Test video playlists', function () {
878 this.timeout(30000) 878 this.timeout(30000)
879 879
880 await updateVideoPlaylistElement({ 880 await updateVideoPlaylistElement({
881 url: servers[ 0 ].url, 881 url: servers[0].url,
882 token: servers[ 0 ].accessToken, 882 token: servers[0].accessToken,
883 playlistId: playlistServer1Id, 883 playlistId: playlistServer1Id,
884 playlistElementId: playlistElementServer1Video4, 884 playlistElementId: playlistElementServer1Video4,
885 elementAttrs: { 885 elementAttrs: {
@@ -888,8 +888,8 @@ describe('Test video playlists', function () {
888 }) 888 })
889 889
890 await updateVideoPlaylistElement({ 890 await updateVideoPlaylistElement({
891 url: servers[ 0 ].url, 891 url: servers[0].url,
892 token: servers[ 0 ].accessToken, 892 token: servers[0].accessToken,
893 playlistId: playlistServer1Id, 893 playlistId: playlistServer1Id,
894 playlistElementId: playlistElementServer1Video5, 894 playlistElementId: playlistElementServer1Video5,
895 elementAttrs: { 895 elementAttrs: {
@@ -903,62 +903,62 @@ describe('Test video playlists', function () {
903 const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) 903 const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
904 const elements: VideoPlaylistElement[] = res.body.data 904 const elements: VideoPlaylistElement[] = res.body.data
905 905
906 expect(elements[ 0 ].video.name).to.equal('video 3 server 1') 906 expect(elements[0].video.name).to.equal('video 3 server 1')
907 expect(elements[ 0 ].position).to.equal(1) 907 expect(elements[0].position).to.equal(1)
908 expect(elements[ 0 ].startTimestamp).to.equal(1) 908 expect(elements[0].startTimestamp).to.equal(1)
909 expect(elements[ 0 ].stopTimestamp).to.equal(35) 909 expect(elements[0].stopTimestamp).to.equal(35)
910 910
911 expect(elements[ 5 ].video.name).to.equal('video 4 server 1') 911 expect(elements[5].video.name).to.equal('video 4 server 1')
912 expect(elements[ 5 ].position).to.equal(6) 912 expect(elements[5].position).to.equal(6)
913 expect(elements[ 5 ].startTimestamp).to.equal(45) 913 expect(elements[5].startTimestamp).to.equal(45)
914 expect(elements[ 5 ].stopTimestamp).to.be.null 914 expect(elements[5].stopTimestamp).to.be.null
915 } 915 }
916 }) 916 })
917 917
918 it('Should check videos existence in my playlist', async function () { 918 it('Should check videos existence in my playlist', async function () {
919 const videoIds = [ 919 const videoIds = [
920 servers[ 0 ].videos[ 0 ].id, 920 servers[0].videos[0].id,
921 42000, 921 42000,
922 servers[ 0 ].videos[ 3 ].id, 922 servers[0].videos[3].id,
923 43000, 923 43000,
924 servers[ 0 ].videos[ 4 ].id 924 servers[0].videos[4].id
925 ] 925 ]
926 const res = await doVideosExistInMyPlaylist(servers[ 0 ].url, servers[ 0 ].accessToken, videoIds) 926 const res = await doVideosExistInMyPlaylist(servers[0].url, servers[0].accessToken, videoIds)
927 const obj = res.body as VideoExistInPlaylist 927 const obj = res.body as VideoExistInPlaylist
928 928
929 { 929 {
930 const elem = obj[ servers[ 0 ].videos[ 0 ].id ] 930 const elem = obj[servers[0].videos[0].id]
931 expect(elem).to.have.lengthOf(1) 931 expect(elem).to.have.lengthOf(1)
932 expect(elem[ 0 ].playlistElementId).to.exist 932 expect(elem[0].playlistElementId).to.exist
933 expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) 933 expect(elem[0].playlistId).to.equal(playlistServer1Id)
934 expect(elem[ 0 ].startTimestamp).to.equal(15) 934 expect(elem[0].startTimestamp).to.equal(15)
935 expect(elem[ 0 ].stopTimestamp).to.equal(28) 935 expect(elem[0].stopTimestamp).to.equal(28)
936 } 936 }
937 937
938 { 938 {
939 const elem = obj[ servers[ 0 ].videos[ 3 ].id ] 939 const elem = obj[servers[0].videos[3].id]
940 expect(elem).to.have.lengthOf(1) 940 expect(elem).to.have.lengthOf(1)
941 expect(elem[ 0 ].playlistElementId).to.equal(playlistElementServer1Video4) 941 expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
942 expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) 942 expect(elem[0].playlistId).to.equal(playlistServer1Id)
943 expect(elem[ 0 ].startTimestamp).to.equal(1) 943 expect(elem[0].startTimestamp).to.equal(1)
944 expect(elem[ 0 ].stopTimestamp).to.equal(35) 944 expect(elem[0].stopTimestamp).to.equal(35)
945 } 945 }
946 946
947 { 947 {
948 const elem = obj[ servers[ 0 ].videos[ 4 ].id ] 948 const elem = obj[servers[0].videos[4].id]
949 expect(elem).to.have.lengthOf(1) 949 expect(elem).to.have.lengthOf(1)
950 expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) 950 expect(elem[0].playlistId).to.equal(playlistServer1Id)
951 expect(elem[ 0 ].startTimestamp).to.equal(45) 951 expect(elem[0].startTimestamp).to.equal(45)
952 expect(elem[ 0 ].stopTimestamp).to.equal(null) 952 expect(elem[0].stopTimestamp).to.equal(null)
953 } 953 }
954 954
955 expect(obj[ 42000 ]).to.have.lengthOf(0) 955 expect(obj[42000]).to.have.lengthOf(0)
956 expect(obj[ 43000 ]).to.have.lengthOf(0) 956 expect(obj[43000]).to.have.lengthOf(0)
957 }) 957 })
958 958
959 it('Should automatically update updatedAt field of playlists', async function () { 959 it('Should automatically update updatedAt field of playlists', async function () {
960 const server = servers[ 1 ] 960 const server = servers[1]
961 const videoId = servers[ 1 ].videos[ 5 ].id 961 const videoId = servers[1].videos[5].id
962 962
963 async function getPlaylistNames () { 963 async function getPlaylistNames () {
964 const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root', 0, 5, undefined, '-updatedAt') 964 const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root', 0, 5, undefined, '-updatedAt')
@@ -974,8 +974,8 @@ describe('Test video playlists', function () {
974 const element2 = res2.body.videoPlaylistElement.id 974 const element2 = res2.body.videoPlaylistElement.id
975 975
976 const names1 = await getPlaylistNames() 976 const names1 = await getPlaylistNames()
977 expect(names1[ 0 ]).to.equal('playlist 3 updated') 977 expect(names1[0]).to.equal('playlist 3 updated')
978 expect(names1[ 1 ]).to.equal('playlist 2') 978 expect(names1[1]).to.equal('playlist 2')
979 979
980 await removeVideoFromPlaylist({ 980 await removeVideoFromPlaylist({
981 url: server.url, 981 url: server.url,
@@ -985,8 +985,8 @@ describe('Test video playlists', function () {
985 }) 985 })
986 986
987 const names2 = await getPlaylistNames() 987 const names2 = await getPlaylistNames()
988 expect(names2[ 0 ]).to.equal('playlist 2') 988 expect(names2[0]).to.equal('playlist 2')
989 expect(names2[ 1 ]).to.equal('playlist 3 updated') 989 expect(names2[1]).to.equal('playlist 3 updated')
990 990
991 await removeVideoFromPlaylist({ 991 await removeVideoFromPlaylist({
992 url: server.url, 992 url: server.url,
@@ -996,23 +996,23 @@ describe('Test video playlists', function () {
996 }) 996 })
997 997
998 const names3 = await getPlaylistNames() 998 const names3 = await getPlaylistNames()
999 expect(names3[ 0 ]).to.equal('playlist 3 updated') 999 expect(names3[0]).to.equal('playlist 3 updated')
1000 expect(names3[ 1 ]).to.equal('playlist 2') 1000 expect(names3[1]).to.equal('playlist 2')
1001 }) 1001 })
1002 1002
1003 it('Should delete some elements', async function () { 1003 it('Should delete some elements', async function () {
1004 this.timeout(30000) 1004 this.timeout(30000)
1005 1005
1006 await removeVideoFromPlaylist({ 1006 await removeVideoFromPlaylist({
1007 url: servers[ 0 ].url, 1007 url: servers[0].url,
1008 token: servers[ 0 ].accessToken, 1008 token: servers[0].accessToken,
1009 playlistId: playlistServer1Id, 1009 playlistId: playlistServer1Id,
1010 playlistElementId: playlistElementServer1Video4 1010 playlistElementId: playlistElementServer1Video4
1011 }) 1011 })
1012 1012
1013 await removeVideoFromPlaylist({ 1013 await removeVideoFromPlaylist({
1014 url: servers[ 0 ].url, 1014 url: servers[0].url,
1015 token: servers[ 0 ].accessToken, 1015 token: servers[0].accessToken,
1016 playlistId: playlistServer1Id, 1016 playlistId: playlistServer1Id,
1017 playlistElementId: playlistElementNSFW 1017 playlistElementId: playlistElementNSFW
1018 }) 1018 })
@@ -1027,17 +1027,17 @@ describe('Test video playlists', function () {
1027 const elements: VideoPlaylistElement[] = res.body.data 1027 const elements: VideoPlaylistElement[] = res.body.data
1028 expect(elements).to.have.lengthOf(4) 1028 expect(elements).to.have.lengthOf(4)
1029 1029
1030 expect(elements[ 0 ].video.name).to.equal('video 0 server 1') 1030 expect(elements[0].video.name).to.equal('video 0 server 1')
1031 expect(elements[ 0 ].position).to.equal(1) 1031 expect(elements[0].position).to.equal(1)
1032 1032
1033 expect(elements[ 1 ].video.name).to.equal('video 2 server 3') 1033 expect(elements[1].video.name).to.equal('video 2 server 3')
1034 expect(elements[ 1 ].position).to.equal(2) 1034 expect(elements[1].position).to.equal(2)
1035 1035
1036 expect(elements[ 2 ].video.name).to.equal('video 1 server 3') 1036 expect(elements[2].video.name).to.equal('video 1 server 3')
1037 expect(elements[ 2 ].position).to.equal(3) 1037 expect(elements[2].position).to.equal(3)
1038 1038
1039 expect(elements[ 3 ].video.name).to.equal('video 4 server 1') 1039 expect(elements[3].video.name).to.equal('video 4 server 1')
1040 expect(elements[ 3 ].position).to.equal(4) 1040 expect(elements[3].position).to.equal(4)
1041 } 1041 }
1042 }) 1042 })
1043 1043
@@ -1045,12 +1045,12 @@ describe('Test video playlists', function () {
1045 this.timeout(30000) 1045 this.timeout(30000)
1046 1046
1047 const res = await createVideoPlaylist({ 1047 const res = await createVideoPlaylist({
1048 url: servers[ 0 ].url, 1048 url: servers[0].url,
1049 token: servers[ 0 ].accessToken, 1049 token: servers[0].accessToken,
1050 playlistAttrs: { 1050 playlistAttrs: {
1051 displayName: 'my super public playlist', 1051 displayName: 'my super public playlist',
1052 privacy: VideoPlaylistPrivacy.PUBLIC, 1052 privacy: VideoPlaylistPrivacy.PUBLIC,
1053 videoChannelId: servers[ 0 ].videoChannel.id 1053 videoChannelId: servers[0].videoChannel.id
1054 } 1054 }
1055 }) 1055 })
1056 const videoPlaylistIds = res.body.videoPlaylist 1056 const videoPlaylistIds = res.body.videoPlaylist
@@ -1062,16 +1062,16 @@ describe('Test video playlists', function () {
1062 } 1062 }
1063 1063
1064 const playlistAttrs = { privacy: VideoPlaylistPrivacy.PRIVATE } 1064 const playlistAttrs = { privacy: VideoPlaylistPrivacy.PRIVATE }
1065 await updateVideoPlaylist({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, playlistId: videoPlaylistIds.id, playlistAttrs }) 1065 await updateVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: videoPlaylistIds.id, playlistAttrs })
1066 1066
1067 await waitJobs(servers) 1067 await waitJobs(servers)
1068 1068
1069 for (const server of [ servers[ 1 ], servers[ 2 ] ]) { 1069 for (const server of [ servers[1], servers[2] ]) {
1070 await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 404) 1070 await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 404)
1071 } 1071 }
1072 await getVideoPlaylist(servers[ 0 ].url, videoPlaylistIds.uuid, 401) 1072 await getVideoPlaylist(servers[0].url, videoPlaylistIds.uuid, 401)
1073 1073
1074 await getVideoPlaylistWithToken(servers[ 0 ].url, servers[ 0 ].accessToken, videoPlaylistIds.uuid, 200) 1074 await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistIds.uuid, 200)
1075 }) 1075 })
1076 }) 1076 })
1077 1077
@@ -1080,7 +1080,7 @@ describe('Test video playlists', function () {
1080 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () { 1080 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
1081 this.timeout(30000) 1081 this.timeout(30000)
1082 1082
1083 await deleteVideoPlaylist(servers[ 0 ].url, servers[ 0 ].accessToken, playlistServer1Id) 1083 await deleteVideoPlaylist(servers[0].url, servers[0].accessToken, playlistServer1Id)
1084 1084
1085 await waitJobs(servers) 1085 await waitJobs(servers)
1086 1086
@@ -1103,15 +1103,15 @@ describe('Test video playlists', function () {
1103 const finder = data => data.find(p => p.displayName === 'my super playlist') 1103 const finder = data => data.find(p => p.displayName === 'my super playlist')
1104 1104
1105 { 1105 {
1106 const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) 1106 const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
1107 expect(res.body.total).to.equal(3) 1107 expect(res.body.total).to.equal(3)
1108 expect(finder(res.body.data)).to.not.be.undefined 1108 expect(finder(res.body.data)).to.not.be.undefined
1109 } 1109 }
1110 1110
1111 await unfollow(servers[ 2 ].url, servers[ 2 ].accessToken, servers[ 0 ]) 1111 await unfollow(servers[2].url, servers[2].accessToken, servers[0])
1112 1112
1113 { 1113 {
1114 const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) 1114 const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
1115 expect(res.body.total).to.equal(1) 1115 expect(res.body.total).to.equal(1)
1116 1116
1117 expect(finder(res.body.data)).to.be.undefined 1117 expect(finder(res.body.data)).to.be.undefined
@@ -1121,12 +1121,12 @@ describe('Test video playlists', function () {
1121 it('Should delete a channel and put the associated playlist in private mode', async function () { 1121 it('Should delete a channel and put the associated playlist in private mode', async function () {
1122 this.timeout(30000) 1122 this.timeout(30000)
1123 1123
1124 const res = await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'super_channel', displayName: 'super channel' }) 1124 const res = await addVideoChannel(servers[0].url, servers[0].accessToken, { name: 'super_channel', displayName: 'super channel' })
1125 const videoChannelId = res.body.videoChannel.id 1125 const videoChannelId = res.body.videoChannel.id
1126 1126
1127 const res2 = await createVideoPlaylist({ 1127 const res2 = await createVideoPlaylist({
1128 url: servers[ 0 ].url, 1128 url: servers[0].url,
1129 token: servers[ 0 ].accessToken, 1129 token: servers[0].accessToken,
1130 playlistAttrs: { 1130 playlistAttrs: {
1131 displayName: 'channel playlist', 1131 displayName: 'channel playlist',
1132 privacy: VideoPlaylistPrivacy.PUBLIC, 1132 privacy: VideoPlaylistPrivacy.PUBLIC,
@@ -1137,15 +1137,15 @@ describe('Test video playlists', function () {
1137 1137
1138 await waitJobs(servers) 1138 await waitJobs(servers)
1139 1139
1140 await deleteVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, 'super_channel') 1140 await deleteVideoChannel(servers[0].url, servers[0].accessToken, 'super_channel')
1141 1141
1142 await waitJobs(servers) 1142 await waitJobs(servers)
1143 1143
1144 const res3 = await getVideoPlaylistWithToken(servers[ 0 ].url, servers[ 0 ].accessToken, videoPlaylistUUID) 1144 const res3 = await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistUUID)
1145 expect(res3.body.displayName).to.equal('channel playlist') 1145 expect(res3.body.displayName).to.equal('channel playlist')
1146 expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE) 1146 expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
1147 1147
1148 await getVideoPlaylist(servers[ 1 ].url, videoPlaylistUUID, 404) 1148 await getVideoPlaylist(servers[1].url, videoPlaylistUUID, 404)
1149 }) 1149 })
1150 1150
1151 it('Should delete an account and delete its playlists', async function () { 1151 it('Should delete an account and delete its playlists', async function () {
@@ -1153,20 +1153,20 @@ describe('Test video playlists', function () {
1153 1153
1154 const user = { username: 'user_1', password: 'password' } 1154 const user = { username: 'user_1', password: 'password' }
1155 const res = await createUser({ 1155 const res = await createUser({
1156 url: servers[ 0 ].url, 1156 url: servers[0].url,
1157 accessToken: servers[ 0 ].accessToken, 1157 accessToken: servers[0].accessToken,
1158 username: user.username, 1158 username: user.username,
1159 password: user.password 1159 password: user.password
1160 }) 1160 })
1161 1161
1162 const userId = res.body.user.id 1162 const userId = res.body.user.id
1163 const userAccessToken = await userLogin(servers[ 0 ], user) 1163 const userAccessToken = await userLogin(servers[0], user)
1164 1164
1165 const resChannel = await getMyUserInformation(servers[ 0 ].url, userAccessToken) 1165 const resChannel = await getMyUserInformation(servers[0].url, userAccessToken)
1166 const userChannel = (resChannel.body as User).videoChannels[ 0 ] 1166 const userChannel = (resChannel.body as User).videoChannels[0]
1167 1167
1168 await createVideoPlaylist({ 1168 await createVideoPlaylist({
1169 url: servers[ 0 ].url, 1169 url: servers[0].url,
1170 token: userAccessToken, 1170 token: userAccessToken,
1171 playlistAttrs: { 1171 playlistAttrs: {
1172 displayName: 'playlist to be deleted', 1172 displayName: 'playlist to be deleted',
@@ -1180,17 +1180,17 @@ describe('Test video playlists', function () {
1180 const finder = data => data.find(p => p.displayName === 'playlist to be deleted') 1180 const finder = data => data.find(p => p.displayName === 'playlist to be deleted')
1181 1181
1182 { 1182 {
1183 for (const server of [ servers[ 0 ], servers[ 1 ] ]) { 1183 for (const server of [ servers[0], servers[1] ]) {
1184 const res = await getVideoPlaylistsList(server.url, 0, 15) 1184 const res = await getVideoPlaylistsList(server.url, 0, 15)
1185 expect(finder(res.body.data)).to.not.be.undefined 1185 expect(finder(res.body.data)).to.not.be.undefined
1186 } 1186 }
1187 } 1187 }
1188 1188
1189 await removeUser(servers[ 0 ].url, userId, servers[ 0 ].accessToken) 1189 await removeUser(servers[0].url, userId, servers[0].accessToken)
1190 await waitJobs(servers) 1190 await waitJobs(servers)
1191 1191
1192 { 1192 {
1193 for (const server of [ servers[ 0 ], servers[ 1 ] ]) { 1193 for (const server of [ servers[0], servers[1] ]) {
1194 const res = await getVideoPlaylistsList(server.url, 0, 15) 1194 const res = await getVideoPlaylistsList(server.url, 0, 15)
1195 expect(finder(res.body.data)).to.be.undefined 1195 expect(finder(res.body.data)).to.be.undefined
1196 } 1196 }
diff --git a/server/tests/api/videos/video-privacy.ts b/server/tests/api/videos/video-privacy.ts
index e630ca84a..4bbbb90f3 100644
--- a/server/tests/api/videos/video-privacy.ts
+++ b/server/tests/api/videos/video-privacy.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -6,7 +6,8 @@ import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enu
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
9 getVideosList, getVideosListWithToken, 9 getVideosList,
10 getVideosListWithToken,
10 ServerInfo, 11 ServerInfo,
11 setAccessTokensToServers, 12 setAccessTokensToServers,
12 uploadVideo 13 uploadVideo
@@ -110,7 +111,7 @@ describe('Test video privacy', function () {
110 username: 'hello', 111 username: 'hello',
111 password: 'super password' 112 password: 'super password'
112 } 113 }
113 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) 114 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
114 115
115 anotherUserToken = await userLogin(servers[0], user) 116 anotherUserToken = await userLogin(servers[0], user)
116 await getVideoWithToken(servers[0].url, anotherUserToken, privateVideoUUID, 403) 117 await getVideoWithToken(servers[0].url, anotherUserToken, privateVideoUUID, 403)
@@ -174,7 +175,7 @@ describe('Test video privacy', function () {
174 privacy: VideoPrivacy.PUBLIC 175 privacy: VideoPrivacy.PUBLIC
175 } 176 }
176 177
177 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, privateVideoId, attribute) 178 await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, attribute)
178 } 179 }
179 180
180 { 181 {
@@ -182,7 +183,7 @@ describe('Test video privacy', function () {
182 name: 'internal video becomes public', 183 name: 'internal video becomes public',
183 privacy: VideoPrivacy.PUBLIC 184 privacy: VideoPrivacy.PUBLIC
184 } 185 }
185 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, internalVideoId, attribute) 186 await updateVideo(servers[0].url, servers[0].accessToken, internalVideoId, attribute)
186 } 187 }
187 188
188 await waitJobs(servers) 189 await waitJobs(servers)
diff --git a/server/tests/api/videos/video-schedule-update.ts b/server/tests/api/videos/video-schedule-update.ts
index 65a8eafb8..204f43611 100644
--- a/server/tests/api/videos/video-schedule-update.ts
+++ b/server/tests/api/videos/video-schedule-update.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -10,7 +10,6 @@ import {
10 getMyVideos, 10 getMyVideos,
11 getVideosList, 11 getVideosList,
12 getVideoWithToken, 12 getVideoWithToken,
13 killallServers,
14 ServerInfo, 13 ServerInfo,
15 setAccessTokensToServers, 14 setAccessTokensToServers,
16 updateVideo, 15 updateVideo,
diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts
index 4be74901a..3e73ccbfa 100644
--- a/server/tests/api/videos/video-transcoder.ts
+++ b/server/tests/api/videos/video-transcoder.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -11,6 +11,7 @@ import {
11 doubleFollow, 11 doubleFollow,
12 flushAndRunMultipleServers, 12 flushAndRunMultipleServers,
13 generateHighBitrateVideo, 13 generateHighBitrateVideo,
14 generateVideoWithFramerate,
14 getMyVideos, 15 getMyVideos,
15 getVideo, 16 getVideo,
16 getVideosList, 17 getVideosList,
@@ -55,19 +56,19 @@ describe('Test video transcoding', function () {
55 56
56 for (const server of servers) { 57 for (const server of servers) {
57 const res = await getVideosList(server.url) 58 const res = await getVideosList(server.url)
58 const video = res.body.data[ 0 ] 59 const video = res.body.data[0]
59 60
60 const res2 = await getVideo(server.url, video.id) 61 const res2 = await getVideo(server.url, video.id)
61 const videoDetails = res2.body 62 const videoDetails = res2.body
62 expect(videoDetails.files).to.have.lengthOf(1) 63 expect(videoDetails.files).to.have.lengthOf(1)
63 64
64 const magnetUri = videoDetails.files[ 0 ].magnetUri 65 const magnetUri = videoDetails.files[0].magnetUri
65 expect(magnetUri).to.match(/\.webm/) 66 expect(magnetUri).to.match(/\.webm/)
66 67
67 const torrent = await webtorrentAdd(magnetUri, true) 68 const torrent = await webtorrentAdd(magnetUri, true)
68 expect(torrent.files).to.be.an('array') 69 expect(torrent.files).to.be.an('array')
69 expect(torrent.files.length).to.equal(1) 70 expect(torrent.files.length).to.equal(1)
70 expect(torrent.files[ 0 ].path).match(/\.webm$/) 71 expect(torrent.files[0].path).match(/\.webm$/)
71 } 72 }
72 }) 73 })
73 74
@@ -92,13 +93,13 @@ describe('Test video transcoding', function () {
92 93
93 expect(videoDetails.files).to.have.lengthOf(4) 94 expect(videoDetails.files).to.have.lengthOf(4)
94 95
95 const magnetUri = videoDetails.files[ 0 ].magnetUri 96 const magnetUri = videoDetails.files[0].magnetUri
96 expect(magnetUri).to.match(/\.mp4/) 97 expect(magnetUri).to.match(/\.mp4/)
97 98
98 const torrent = await webtorrentAdd(magnetUri, true) 99 const torrent = await webtorrentAdd(magnetUri, true)
99 expect(torrent.files).to.be.an('array') 100 expect(torrent.files).to.be.an('array')
100 expect(torrent.files.length).to.equal(1) 101 expect(torrent.files.length).to.equal(1)
101 expect(torrent.files[ 0 ].path).match(/\.mp4$/) 102 expect(torrent.files[0].path).match(/\.mp4$/)
102 } 103 }
103 }) 104 })
104 105
@@ -126,8 +127,8 @@ describe('Test video transcoding', function () {
126 const probe = await audio.get(path) 127 const probe = await audio.get(path)
127 128
128 if (probe.audioStream) { 129 if (probe.audioStream) {
129 expect(probe.audioStream[ 'codec_name' ]).to.be.equal('aac') 130 expect(probe.audioStream['codec_name']).to.be.equal('aac')
130 expect(probe.audioStream[ 'bit_rate' ]).to.be.at.most(384 * 8000) 131 expect(probe.audioStream['bit_rate']).to.be.at.most(384 * 8000)
131 } else { 132 } else {
132 this.fail('Could not retrieve the audio stream on ' + probe.absolutePath) 133 this.fail('Could not retrieve the audio stream on ' + probe.absolutePath)
133 } 134 }
@@ -211,10 +212,10 @@ describe('Test video transcoding', function () {
211 const videoDetails: VideoDetails = res2.body 212 const videoDetails: VideoDetails = res2.body
212 213
213 expect(videoDetails.files).to.have.lengthOf(4) 214 expect(videoDetails.files).to.have.lengthOf(4)
214 expect(videoDetails.files[ 0 ].fps).to.be.above(58).and.below(62) 215 expect(videoDetails.files[0].fps).to.be.above(58).and.below(62)
215 expect(videoDetails.files[ 1 ].fps).to.be.below(31) 216 expect(videoDetails.files[1].fps).to.be.below(31)
216 expect(videoDetails.files[ 2 ].fps).to.be.below(31) 217 expect(videoDetails.files[2].fps).to.be.below(31)
217 expect(videoDetails.files[ 3 ].fps).to.be.below(31) 218 expect(videoDetails.files[3].fps).to.be.below(31)
218 219
219 for (const resolution of [ '240', '360', '480' ]) { 220 for (const resolution of [ '240', '360', '480' ]) {
220 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4') 221 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4')
@@ -240,11 +241,11 @@ describe('Test video transcoding', function () {
240 fixture: 'video_short1.webm', 241 fixture: 'video_short1.webm',
241 waitTranscoding: true 242 waitTranscoding: true
242 } 243 }
243 const resVideo = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes) 244 const resVideo = await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
244 const videoId = resVideo.body.video.uuid 245 const videoId = resVideo.body.video.uuid
245 246
246 // Should be in transcode state 247 // Should be in transcode state
247 const { body } = await getVideo(servers[ 1 ].url, videoId) 248 const { body } = await getVideo(servers[1].url, videoId)
248 expect(body.name).to.equal('waiting video') 249 expect(body.name).to.equal('waiting video')
249 expect(body.state.id).to.equal(VideoState.TO_TRANSCODE) 250 expect(body.state.id).to.equal(VideoState.TO_TRANSCODE)
250 expect(body.state.label).to.equal('To transcode') 251 expect(body.state.label).to.equal('To transcode')
@@ -310,7 +311,7 @@ describe('Test video transcoding', function () {
310 311
311 const video = res.body.data.find(v => v.name === videoAttributes.name) 312 const video = res.body.data.find(v => v.name === videoAttributes.name)
312 313
313 for (const resolution of ['240', '360', '480', '720', '1080']) { 314 for (const resolution of [ '240', '360', '480', '720', '1080' ]) {
314 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4') 315 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4')
315 const bitrate = await getVideoFileBitrate(path) 316 const bitrate = await getVideoFileBitrate(path)
316 const fps = await getVideoFileFPS(path) 317 const fps = await getVideoFileFPS(path)
@@ -340,7 +341,7 @@ describe('Test video transcoding', function () {
340 fixture 341 fixture
341 } 342 }
342 343
343 await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes) 344 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
344 345
345 await waitJobs(servers) 346 await waitJobs(servers)
346 347
@@ -353,7 +354,7 @@ describe('Test video transcoding', function () {
353 354
354 expect(videoDetails.files).to.have.lengthOf(4) 355 expect(videoDetails.files).to.have.lengthOf(4)
355 356
356 const magnetUri = videoDetails.files[ 0 ].magnetUri 357 const magnetUri = videoDetails.files[0].magnetUri
357 expect(magnetUri).to.contain('.mp4') 358 expect(magnetUri).to.contain('.mp4')
358 } 359 }
359 } 360 }
@@ -370,7 +371,7 @@ describe('Test video transcoding', function () {
370 this.timeout(60000) 371 this.timeout(60000)
371 372
372 const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' } 373 const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' }
373 await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributesArg) 374 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributesArg)
374 375
375 await waitJobs(servers) 376 await waitJobs(servers)
376 377
@@ -386,7 +387,7 @@ describe('Test video transcoding', function () {
386 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 }) 387 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 })
387 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 }) 388 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 })
388 389
389 const magnetUri = videoDetails.files[ 0 ].magnetUri 390 const magnetUri = videoDetails.files[0].magnetUri
390 expect(magnetUri).to.contain('.mp4') 391 expect(magnetUri).to.contain('.mp4')
391 } 392 }
392 }) 393 })
@@ -395,7 +396,7 @@ describe('Test video transcoding', function () {
395 this.timeout(60000) 396 this.timeout(60000)
396 397
397 const videoAttributesArg = { name: 'audio_without_preview', fixture: 'sample.ogg' } 398 const videoAttributesArg = { name: 'audio_without_preview', fixture: 'sample.ogg' }
398 await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributesArg) 399 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributesArg)
399 400
400 await waitJobs(servers) 401 await waitJobs(servers)
401 402
@@ -411,11 +412,52 @@ describe('Test video transcoding', function () {
411 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 }) 412 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 })
412 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 }) 413 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 })
413 414
414 const magnetUri = videoDetails.files[ 0 ].magnetUri 415 const magnetUri = videoDetails.files[0].magnetUri
415 expect(magnetUri).to.contain('.mp4') 416 expect(magnetUri).to.contain('.mp4')
416 } 417 }
417 }) 418 })
418 419
420 it('Should downscale to the closest divisor standard framerate', async function () {
421 this.timeout(160000)
422
423 let tempFixturePath: string
424
425 {
426 tempFixturePath = await generateVideoWithFramerate(59)
427
428 const fps = await getVideoFileFPS(tempFixturePath)
429 expect(fps).to.be.equal(59)
430 }
431
432 const videoAttributes = {
433 name: '59fps video',
434 description: '59fps video',
435 fixture: tempFixturePath
436 }
437
438 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
439
440 await waitJobs(servers)
441
442 for (const server of servers) {
443 const res = await getVideosList(server.url)
444
445 const video = res.body.data.find(v => v.name === videoAttributes.name)
446
447 {
448 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-240.mp4')
449 const fps = await getVideoFileFPS(path)
450 expect(fps).to.be.equal(25)
451 }
452
453 {
454 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-720.mp4')
455 const fps = await getVideoFileFPS(path)
456 expect(fps).to.be.equal(59)
457 }
458 }
459 })
460
419 after(async function () { 461 after(async function () {
420 await cleanupTests(servers) 462 await cleanupTests(servers)
421 }) 463 })
diff --git a/server/tests/api/videos/videos-filter.ts b/server/tests/api/videos/videos-filter.ts
index e1e65260f..95e12e43c 100644
--- a/server/tests/api/videos/videos-filter.ts
+++ b/server/tests/api/videos/videos-filter.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -7,8 +7,6 @@ import {
7 createUser, 7 createUser,
8 doubleFollow, 8 doubleFollow,
9 flushAndRunMultipleServers, 9 flushAndRunMultipleServers,
10 flushTests,
11 killallServers,
12 makeGetRequest, 10 makeGetRequest,
13 ServerInfo, 11 ServerInfo,
14 setAccessTokensToServers, 12 setAccessTokensToServers,
@@ -98,7 +96,7 @@ describe('Test videos filter validator', function () {
98 const namesResults = await getVideosNames(server, server.accessToken, 'local') 96 const namesResults = await getVideosNames(server, server.accessToken, 'local')
99 for (const names of namesResults) { 97 for (const names of namesResults) {
100 expect(names).to.have.lengthOf(1) 98 expect(names).to.have.lengthOf(1)
101 expect(names[ 0 ]).to.equal('public ' + server.serverNumber) 99 expect(names[0]).to.equal('public ' + server.serverNumber)
102 } 100 }
103 } 101 }
104 }) 102 })
@@ -111,9 +109,9 @@ describe('Test videos filter validator', function () {
111 for (const names of namesResults) { 109 for (const names of namesResults) {
112 expect(names).to.have.lengthOf(3) 110 expect(names).to.have.lengthOf(3)
113 111
114 expect(names[ 0 ]).to.equal('public ' + server.serverNumber) 112 expect(names[0]).to.equal('public ' + server.serverNumber)
115 expect(names[ 1 ]).to.equal('unlisted ' + server.serverNumber) 113 expect(names[1]).to.equal('unlisted ' + server.serverNumber)
116 expect(names[ 2 ]).to.equal('private ' + server.serverNumber) 114 expect(names[2]).to.equal('private ' + server.serverNumber)
117 } 115 }
118 } 116 }
119 } 117 }
diff --git a/server/tests/api/videos/videos-history.ts b/server/tests/api/videos/videos-history.ts
index c7e55c1ab..6f90e9a57 100644
--- a/server/tests/api/videos/videos-history.ts
+++ b/server/tests/api/videos/videos-history.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/videos/videos-overview.ts b/server/tests/api/videos/videos-overview.ts
index 975a5c87a..ca08ab5b1 100644
--- a/server/tests/api/videos/videos-overview.ts
+++ b/server/tests/api/videos/videos-overview.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/videos/videos-views-cleaner.ts b/server/tests/api/videos/videos-views-cleaner.ts
index fbddd40f4..d063d7973 100644
--- a/server/tests/api/videos/videos-views-cleaner.ts
+++ b/server/tests/api/videos/videos-views-cleaner.ts
@@ -1,20 +1,22 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 cleanupTests,
7 closeAllSequelize,
8 countVideoViewsOf,
9 doubleFollow,
6 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
7 flushTests,
8 killallServers, 11 killallServers,
9 reRunServer, 12 reRunServer,
10 flushAndRunServer,
11 ServerInfo, 13 ServerInfo,
12 setAccessTokensToServers, 14 setAccessTokensToServers,
13 uploadVideo, uploadVideoAndGetId, viewVideo, wait, countVideoViewsOf, doubleFollow, waitJobs, cleanupTests, closeAllSequelize 15 uploadVideoAndGetId,
16 viewVideo,
17 wait,
18 waitJobs
14} from '../../../../shared/extra-utils' 19} from '../../../../shared/extra-utils'
15import { getVideosOverview } from '../../../../shared/extra-utils/overviews/overviews'
16import { VideosOverview } from '../../../../shared/models/overviews'
17import { listMyVideosHistory } from '../../../../shared/extra-utils/videos/video-history'
18 20
19const expect = chai.expect 21const expect = chai.expect
20 22
diff --git a/server/tests/cli/create-import-video-file-job.ts b/server/tests/cli/create-import-video-file-job.ts
index aca3216bb..dac049fe4 100644
--- a/server/tests/cli/create-import-video-file-job.ts
+++ b/server/tests/cli/create-import-video-file-job.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
@@ -71,7 +71,7 @@ describe('Test create import video jobs', function () {
71 const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body 71 const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body
72 72
73 expect(videoDetail.files).to.have.lengthOf(2) 73 expect(videoDetail.files).to.have.lengthOf(2)
74 const [originalVideo, transcodedVideo] = videoDetail.files 74 const [ originalVideo, transcodedVideo ] = videoDetail.files
75 assertVideoProperties(originalVideo, 720, 'webm', 218910) 75 assertVideoProperties(originalVideo, 720, 'webm', 218910)
76 assertVideoProperties(transcodedVideo, 480, 'webm', 69217) 76 assertVideoProperties(transcodedVideo, 480, 'webm', 69217)
77 77
@@ -95,7 +95,7 @@ describe('Test create import video jobs', function () {
95 const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body 95 const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body
96 96
97 expect(videoDetail.files).to.have.lengthOf(4) 97 expect(videoDetail.files).to.have.lengthOf(4)
98 const [originalVideo, transcodedVideo420, transcodedVideo320, transcodedVideo240] = videoDetail.files 98 const [ originalVideo, transcodedVideo420, transcodedVideo320, transcodedVideo240 ] = videoDetail.files
99 assertVideoProperties(originalVideo, 720, 'ogv', 140849) 99 assertVideoProperties(originalVideo, 720, 'ogv', 140849)
100 assertVideoProperties(transcodedVideo420, 480, 'mp4') 100 assertVideoProperties(transcodedVideo420, 480, 'mp4')
101 assertVideoProperties(transcodedVideo320, 360, 'mp4') 101 assertVideoProperties(transcodedVideo320, 360, 'mp4')
diff --git a/server/tests/cli/create-transcoding-job.ts b/server/tests/cli/create-transcoding-job.ts
index 7897ff1b3..997a9a1fd 100644
--- a/server/tests/cli/create-transcoding-job.ts
+++ b/server/tests/cli/create-transcoding-job.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
@@ -8,14 +8,13 @@ import {
8 doubleFollow, 8 doubleFollow,
9 execCLI, 9 execCLI,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
11 flushTests,
12 getEnvCli, 11 getEnvCli,
13 getVideo, 12 getVideo,
14 getVideosList, 13 getVideosList,
15 killallServers,
16 ServerInfo, 14 ServerInfo,
17 setAccessTokensToServers, updateCustomSubConfig, 15 setAccessTokensToServers,
18 uploadVideo, wait 16 updateCustomSubConfig,
17 uploadVideo
19} from '../../../shared/extra-utils' 18} from '../../../shared/extra-utils'
20import { waitJobs } from '../../../shared/extra-utils/server/jobs' 19import { waitJobs } from '../../../shared/extra-utils/server/jobs'
21 20
@@ -23,7 +22,7 @@ const expect = chai.expect
23 22
24describe('Test create transcoding jobs', function () { 23describe('Test create transcoding jobs', function () {
25 let servers: ServerInfo[] = [] 24 let servers: ServerInfo[] = []
26 let videosUUID: string[] = [] 25 const videosUUID: string[] = []
27 26
28 const config = { 27 const config = {
29 transcoding: { 28 transcoding: {
@@ -54,7 +53,7 @@ describe('Test create transcoding jobs', function () {
54 await doubleFollow(servers[0], servers[1]) 53 await doubleFollow(servers[0], servers[1])
55 54
56 for (let i = 1; i <= 5; i++) { 55 for (let i = 1; i <= 5; i++) {
57 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video' + i }) 56 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' + i })
58 videosUUID.push(res.body.video.uuid) 57 videosUUID.push(res.body.video.uuid)
59 } 58 }
60 59
@@ -90,7 +89,7 @@ describe('Test create transcoding jobs', function () {
90 const res = await getVideosList(server.url) 89 const res = await getVideosList(server.url)
91 const videos = res.body.data 90 const videos = res.body.data
92 91
93 let infoHashes: { [ id: number ]: string } 92 let infoHashes: { [id: number]: string }
94 93
95 for (const video of videos) { 94 for (const video of videos) {
96 const res2 = await getVideo(server.url, video.uuid) 95 const res2 = await getVideo(server.url, video.uuid)
diff --git a/server/tests/cli/optimize-old-videos.ts b/server/tests/cli/optimize-old-videos.ts
index de5d672f5..e2e13598f 100644
--- a/server/tests/cli/optimize-old-videos.ts
+++ b/server/tests/cli/optimize-old-videos.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
@@ -28,7 +28,9 @@ const expect = chai.expect
28 28
29describe('Test optimize old videos', function () { 29describe('Test optimize old videos', function () {
30 let servers: ServerInfo[] = [] 30 let servers: ServerInfo[] = []
31 // eslint-disable-next-line @typescript-eslint/no-unused-vars
31 let video1UUID: string 32 let video1UUID: string
33 // eslint-disable-next-line @typescript-eslint/no-unused-vars
32 let video2UUID: string 34 let video2UUID: string
33 35
34 before(async function () { 36 before(async function () {
diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts
index b8c0b1f79..27fbde02d 100644
--- a/server/tests/cli/peertube.ts
+++ b/server/tests/cli/peertube.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
@@ -7,14 +7,17 @@ import {
7 buildAbsoluteFixturePath, 7 buildAbsoluteFixturePath,
8 cleanupTests, 8 cleanupTests,
9 createUser, 9 createUser,
10 doubleFollow,
10 execCLI, 11 execCLI,
11 flushAndRunServer, 12 flushAndRunServer,
12 getEnvCli, 13 getEnvCli,
14 getLocalIdByUUID,
13 getVideo, 15 getVideo,
14 getVideosList, 16 getVideosList,
15 getVideosListWithToken, removeVideo, 17 removeVideo,
16 ServerInfo, 18 ServerInfo,
17 setAccessTokensToServers, 19 setAccessTokensToServers,
20 uploadVideoAndGetId,
18 userLogin, 21 userLogin,
19 waitJobs 22 waitJobs
20} from '../../../shared/extra-utils' 23} from '../../../shared/extra-utils'
@@ -101,7 +104,7 @@ describe('Test CLI wrapper', function () {
101 104
102 const videos: Video[] = res.body.data 105 const videos: Video[] = res.body.data
103 106
104 const video: VideoDetails = (await getVideo(server.url, videos[ 0 ].uuid)).body 107 const video: VideoDetails = (await getVideo(server.url, videos[0].uuid)).body
105 108
106 expect(video.name).to.equal('test upload') 109 expect(video.name).to.equal('test upload')
107 expect(video.support).to.equal('support_text') 110 expect(video.support).to.equal('support_text')
@@ -210,6 +213,81 @@ describe('Test CLI wrapper', function () {
210 }) 213 })
211 }) 214 })
212 215
216 describe('Manage video redundancies', function () {
217 let anotherServer: ServerInfo
218 let video1Server2: number
219 let servers: ServerInfo[]
220
221 before(async function () {
222 this.timeout(120000)
223
224 anotherServer = await flushAndRunServer(2)
225 await setAccessTokensToServers([ anotherServer ])
226
227 await doubleFollow(server, anotherServer)
228
229 servers = [ server, anotherServer ]
230 await waitJobs(servers)
231
232 const uuid = (await uploadVideoAndGetId({ server: anotherServer, videoName: 'super video' })).uuid
233 await waitJobs(servers)
234
235 video1Server2 = await getLocalIdByUUID(server.url, uuid)
236 })
237
238 it('Should add a redundancy', async function () {
239 this.timeout(60000)
240
241 const env = getEnvCli(server)
242
243 const params = `add --video ${video1Server2}`
244
245 await execCLI(`${env} ${cmd} redundancy ${params}`)
246
247 await waitJobs(servers)
248 })
249
250 it('Should list redundancies', async function () {
251 this.timeout(60000)
252
253 {
254 const env = getEnvCli(server)
255
256 const params = 'list-my-redundancies'
257 const stdout = await execCLI(`${env} ${cmd} redundancy ${params}`)
258
259 expect(stdout).to.contain('super video')
260 expect(stdout).to.contain(`localhost:${server.port}`)
261 }
262 })
263
264 it('Should remove a redundancy', async function () {
265 this.timeout(60000)
266
267 const env = getEnvCli(server)
268
269 const params = `remove --video ${video1Server2}`
270
271 await execCLI(`${env} ${cmd} redundancy ${params}`)
272
273 await waitJobs(servers)
274
275 {
276 const env = getEnvCli(server)
277 const params = 'list-my-redundancies'
278 const stdout = await execCLI(`${env} ${cmd} redundancy ${params}`)
279
280 expect(stdout).to.not.contain('super video')
281 }
282 })
283
284 after(async function () {
285 this.timeout(10000)
286
287 await cleanupTests([ anotherServer ])
288 })
289 })
290
213 after(async function () { 291 after(async function () {
214 this.timeout(10000) 292 this.timeout(10000)
215 293
diff --git a/server/tests/cli/plugins.ts b/server/tests/cli/plugins.ts
index a5257d671..7f19f14b7 100644
--- a/server/tests/cli/plugins.ts
+++ b/server/tests/cli/plugins.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import {
diff --git a/server/tests/cli/prune-storage.ts b/server/tests/cli/prune-storage.ts
index 144e67c44..304c8ca56 100644
--- a/server/tests/cli/prune-storage.ts
+++ b/server/tests/cli/prune-storage.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
@@ -11,9 +11,11 @@ import {
11 execCLI, 11 execCLI,
12 flushAndRunMultipleServers, 12 flushAndRunMultipleServers,
13 getAccount, 13 getAccount,
14 getEnvCli, makeGetRequest, makeRawRequest, 14 getEnvCli,
15 makeGetRequest,
15 ServerInfo, 16 ServerInfo,
16 setAccessTokensToServers, setDefaultVideoChannel, 17 setAccessTokensToServers,
18 setDefaultVideoChannel,
17 updateMyAvatar, 19 updateMyAvatar,
18 uploadVideo, 20 uploadVideo,
19 wait 21 wait
@@ -22,7 +24,6 @@ import { Account, VideoPlaylistPrivacy } from '../../../shared/models'
22import { createFile, readdir } from 'fs-extra' 24import { createFile, readdir } from 'fs-extra'
23import * as uuidv4 from 'uuid/v4' 25import * as uuidv4 from 'uuid/v4'
24import { join } from 'path' 26import { join } from 'path'
25import * as request from 'supertest'
26 27
27const expect = chai.expect 28const expect = chai.expect
28 29
@@ -61,7 +62,7 @@ async function assertCountAreOkay (servers: ServerInfo[]) {
61 62
62describe('Test prune storage scripts', function () { 63describe('Test prune storage scripts', function () {
63 let servers: ServerInfo[] 64 let servers: ServerInfo[]
64 const badNames: { [ directory: string ]: string[] } = {} 65 const badNames: { [directory: string]: string[] } = {}
65 66
66 before(async function () { 67 before(async function () {
67 this.timeout(120000) 68 this.timeout(120000)
@@ -92,20 +93,20 @@ describe('Test prune storage scripts', function () {
92 93
93 // Lazy load the remote avatar 94 // Lazy load the remote avatar
94 { 95 {
95 const res = await getAccount(servers[ 0 ].url, 'root@localhost:' + servers[ 1 ].port) 96 const res = await getAccount(servers[0].url, 'root@localhost:' + servers[1].port)
96 const account: Account = res.body 97 const account: Account = res.body
97 await makeGetRequest({ 98 await makeGetRequest({
98 url: servers[ 0 ].url, 99 url: servers[0].url,
99 path: account.avatar.path, 100 path: account.avatar.path,
100 statusCodeExpected: 200 101 statusCodeExpected: 200
101 }) 102 })
102 } 103 }
103 104
104 { 105 {
105 const res = await getAccount(servers[ 1 ].url, 'root@localhost:' + servers[ 0 ].port) 106 const res = await getAccount(servers[1].url, 'root@localhost:' + servers[0].port)
106 const account: Account = res.body 107 const account: Account = res.body
107 await makeGetRequest({ 108 await makeGetRequest({
108 url: servers[ 1 ].url, 109 url: servers[1].url,
109 path: account.avatar.path, 110 path: account.avatar.path,
110 statusCodeExpected: 200 111 statusCodeExpected: 200
111 }) 112 })
diff --git a/server/tests/cli/update-host.ts b/server/tests/cli/update-host.ts
index 55c43b32f..2070f16f5 100644
--- a/server/tests/cli/update-host.ts
+++ b/server/tests/cli/update-host.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
diff --git a/server/tests/client.ts b/server/tests/client.ts
index 778dcd08e..d61724e51 100644
--- a/server/tests/client.ts
+++ b/server/tests/client.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts
index 437470327..4510177cc 100644
--- a/server/tests/feeds/feeds.ts
+++ b/server/tests/feeds/feeds.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -51,7 +51,7 @@ describe('Test syndication feeds', () => {
51 51
52 { 52 {
53 const attr = { username: 'john', password: 'password' } 53 const attr = { username: 'john', password: 'password' }
54 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: attr.username, password: attr.password }) 54 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: attr.username, password: attr.password })
55 userAccessToken = await userLogin(servers[0], attr) 55 userAccessToken = await userLogin(servers[0], attr)
56 56
57 const res = await getMyUserInformation(servers[0].url, userAccessToken) 57 const res = await getMyUserInformation(servers[0].url, userAccessToken)
@@ -61,7 +61,7 @@ describe('Test syndication feeds', () => {
61 } 61 }
62 62
63 { 63 {
64 await uploadVideo(servers[ 0 ].url, userAccessToken, { name: 'user video' }) 64 await uploadVideo(servers[0].url, userAccessToken, { name: 'user video' })
65 } 65 }
66 66
67 { 67 {
@@ -70,11 +70,11 @@ describe('Test syndication feeds', () => {
70 description: 'my super description for server 1', 70 description: 'my super description for server 1',
71 fixture: 'video_short.webm' 71 fixture: 'video_short.webm'
72 } 72 }
73 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributes) 73 const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
74 const videoId = res.body.video.id 74 const videoId = res.body.video.id
75 75
76 await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoId, 'super comment 1') 76 await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'super comment 1')
77 await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoId, 'super comment 2') 77 await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'super comment 2')
78 } 78 }
79 79
80 await waitJobs(servers) 80 await waitJobs(servers)
@@ -84,18 +84,18 @@ describe('Test syndication feeds', () => {
84 84
85 it('Should be well formed XML (covers RSS 2.0 and ATOM 1.0 endpoints)', async function () { 85 it('Should be well formed XML (covers RSS 2.0 and ATOM 1.0 endpoints)', async function () {
86 for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) { 86 for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) {
87 const rss = await getXMLfeed(servers[ 0 ].url, feed) 87 const rss = await getXMLfeed(servers[0].url, feed)
88 expect(rss.text).xml.to.be.valid() 88 expect(rss.text).xml.to.be.valid()
89 89
90 const atom = await getXMLfeed(servers[ 0 ].url, feed, 'atom') 90 const atom = await getXMLfeed(servers[0].url, feed, 'atom')
91 expect(atom.text).xml.to.be.valid() 91 expect(atom.text).xml.to.be.valid()
92 } 92 }
93 }) 93 })
94 94
95 it('Should be well formed JSON (covers JSON feed 1.0 endpoint)', async function () { 95 it('Should be well formed JSON (covers JSON feed 1.0 endpoint)', async function () {
96 for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) { 96 for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) {
97 const json = await getJSONfeed(servers[ 0 ].url, feed) 97 const json = await getJSONfeed(servers[0].url, feed)
98 expect(JSON.parse(json.text)).to.be.jsonSchema({ 'type': 'object' }) 98 expect(JSON.parse(json.text)).to.be.jsonSchema({ type: 'object' })
99 } 99 }
100 }) 100 })
101 }) 101 })
@@ -118,11 +118,11 @@ describe('Test syndication feeds', () => {
118 const json = await getJSONfeed(server.url, 'videos') 118 const json = await getJSONfeed(server.url, 'videos')
119 const jsonObj = JSON.parse(json.text) 119 const jsonObj = JSON.parse(json.text)
120 expect(jsonObj.items.length).to.be.equal(2) 120 expect(jsonObj.items.length).to.be.equal(2)
121 expect(jsonObj.items[ 0 ].attachments).to.exist 121 expect(jsonObj.items[0].attachments).to.exist
122 expect(jsonObj.items[ 0 ].attachments.length).to.be.eq(1) 122 expect(jsonObj.items[0].attachments.length).to.be.eq(1)
123 expect(jsonObj.items[ 0 ].attachments[ 0 ].mime_type).to.be.eq('application/x-bittorrent') 123 expect(jsonObj.items[0].attachments[0].mime_type).to.be.eq('application/x-bittorrent')
124 expect(jsonObj.items[ 0 ].attachments[ 0 ].size_in_bytes).to.be.eq(218910) 124 expect(jsonObj.items[0].attachments[0].size_in_bytes).to.be.eq(218910)
125 expect(jsonObj.items[ 0 ].attachments[ 0 ].url).to.contain('720.torrent') 125 expect(jsonObj.items[0].attachments[0].url).to.contain('720.torrent')
126 } 126 }
127 }) 127 })
128 128
@@ -131,16 +131,16 @@ describe('Test syndication feeds', () => {
131 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: rootAccountId }) 131 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: rootAccountId })
132 const jsonObj = JSON.parse(json.text) 132 const jsonObj = JSON.parse(json.text)
133 expect(jsonObj.items.length).to.be.equal(1) 133 expect(jsonObj.items.length).to.be.equal(1)
134 expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1') 134 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
135 expect(jsonObj.items[ 0 ].author.name).to.equal('root') 135 expect(jsonObj.items[0].author.name).to.equal('root')
136 } 136 }
137 137
138 { 138 {
139 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId }) 139 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId })
140 const jsonObj = JSON.parse(json.text) 140 const jsonObj = JSON.parse(json.text)
141 expect(jsonObj.items.length).to.be.equal(1) 141 expect(jsonObj.items.length).to.be.equal(1)
142 expect(jsonObj.items[ 0 ].title).to.equal('user video') 142 expect(jsonObj.items[0].title).to.equal('user video')
143 expect(jsonObj.items[ 0 ].author.name).to.equal('john') 143 expect(jsonObj.items[0].author.name).to.equal('john')
144 } 144 }
145 145
146 for (const server of servers) { 146 for (const server of servers) {
@@ -148,14 +148,14 @@ describe('Test syndication feeds', () => {
148 const json = await getJSONfeed(server.url, 'videos', { accountName: 'root@localhost:' + servers[0].port }) 148 const json = await getJSONfeed(server.url, 'videos', { accountName: 'root@localhost:' + servers[0].port })
149 const jsonObj = JSON.parse(json.text) 149 const jsonObj = JSON.parse(json.text)
150 expect(jsonObj.items.length).to.be.equal(1) 150 expect(jsonObj.items.length).to.be.equal(1)
151 expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1') 151 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
152 } 152 }
153 153
154 { 154 {
155 const json = await getJSONfeed(server.url, 'videos', { accountName: 'john@localhost:' + servers[0].port }) 155 const json = await getJSONfeed(server.url, 'videos', { accountName: 'john@localhost:' + servers[0].port })
156 const jsonObj = JSON.parse(json.text) 156 const jsonObj = JSON.parse(json.text)
157 expect(jsonObj.items.length).to.be.equal(1) 157 expect(jsonObj.items.length).to.be.equal(1)
158 expect(jsonObj.items[ 0 ].title).to.equal('user video') 158 expect(jsonObj.items[0].title).to.equal('user video')
159 } 159 }
160 } 160 }
161 }) 161 })
@@ -165,16 +165,16 @@ describe('Test syndication feeds', () => {
165 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: rootChannelId }) 165 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: rootChannelId })
166 const jsonObj = JSON.parse(json.text) 166 const jsonObj = JSON.parse(json.text)
167 expect(jsonObj.items.length).to.be.equal(1) 167 expect(jsonObj.items.length).to.be.equal(1)
168 expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1') 168 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
169 expect(jsonObj.items[ 0 ].author.name).to.equal('root') 169 expect(jsonObj.items[0].author.name).to.equal('root')
170 } 170 }
171 171
172 { 172 {
173 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: userChannelId }) 173 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: userChannelId })
174 const jsonObj = JSON.parse(json.text) 174 const jsonObj = JSON.parse(json.text)
175 expect(jsonObj.items.length).to.be.equal(1) 175 expect(jsonObj.items.length).to.be.equal(1)
176 expect(jsonObj.items[ 0 ].title).to.equal('user video') 176 expect(jsonObj.items[0].title).to.equal('user video')
177 expect(jsonObj.items[ 0 ].author.name).to.equal('john') 177 expect(jsonObj.items[0].author.name).to.equal('john')
178 } 178 }
179 179
180 for (const server of servers) { 180 for (const server of servers) {
@@ -182,14 +182,14 @@ describe('Test syndication feeds', () => {
182 const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'root_channel@localhost:' + servers[0].port }) 182 const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'root_channel@localhost:' + servers[0].port })
183 const jsonObj = JSON.parse(json.text) 183 const jsonObj = JSON.parse(json.text)
184 expect(jsonObj.items.length).to.be.equal(1) 184 expect(jsonObj.items.length).to.be.equal(1)
185 expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1') 185 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
186 } 186 }
187 187
188 { 188 {
189 const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'john_channel@localhost:' + servers[0].port }) 189 const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'john_channel@localhost:' + servers[0].port })
190 const jsonObj = JSON.parse(json.text) 190 const jsonObj = JSON.parse(json.text)
191 expect(jsonObj.items.length).to.be.equal(1) 191 expect(jsonObj.items.length).to.be.equal(1)
192 expect(jsonObj.items[ 0 ].title).to.equal('user video') 192 expect(jsonObj.items[0].title).to.equal('user video')
193 } 193 }
194 } 194 }
195 }) 195 })
@@ -202,8 +202,8 @@ describe('Test syndication feeds', () => {
202 202
203 const jsonObj = JSON.parse(json.text) 203 const jsonObj = JSON.parse(json.text)
204 expect(jsonObj.items.length).to.be.equal(2) 204 expect(jsonObj.items.length).to.be.equal(2)
205 expect(jsonObj.items[ 0 ].html_content).to.equal('super comment 2') 205 expect(jsonObj.items[0].html_content).to.equal('super comment 2')
206 expect(jsonObj.items[ 1 ].html_content).to.equal('super comment 1') 206 expect(jsonObj.items[1].html_content).to.equal('super comment 1')
207 } 207 }
208 }) 208 })
209 }) 209 })
diff --git a/server/tests/helpers/comment-model.ts b/server/tests/helpers/comment-model.ts
index ebfd779e1..4c51b7000 100644
--- a/server/tests/helpers/comment-model.ts
+++ b/server/tests/helpers/comment-model.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -20,7 +20,7 @@ describe('Comment model', function () {
20 20
21 comment.text = '@florian @jean@localhost:9000 @flo @another@localhost:9000 @flo2@jean.com hello ' + 21 comment.text = '@florian @jean@localhost:9000 @flo @another@localhost:9000 @flo2@jean.com hello ' +
22 'email@localhost:9000 coucou.com no? @chocobozzz @chocobozzz @end' 22 'email@localhost:9000 coucou.com no? @chocobozzz @chocobozzz @end'
23 const result = comment.extractMentions().sort() 23 const result = comment.extractMentions().sort((a, b) => a.localeCompare(b))
24 24
25 expect(result).to.deep.equal([ 'another', 'chocobozzz', 'end', 'flo', 'florian', 'jean' ]) 25 expect(result).to.deep.equal([ 'another', 'chocobozzz', 'end', 'flo', 'florian', 'jean' ])
26 }) 26 })
diff --git a/server/tests/helpers/core-utils.ts b/server/tests/helpers/core-utils.ts
index 31fc6dd7c..c028b316d 100644
--- a/server/tests/helpers/core-utils.ts
+++ b/server/tests/helpers/core-utils.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/helpers/request.ts b/server/tests/helpers/request.ts
index a754bc6e2..f8b2d599b 100644
--- a/server/tests/helpers/request.ts
+++ b/server/tests/helpers/request.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' 4import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
diff --git a/server/tests/misc-endpoints.ts b/server/tests/misc-endpoints.ts
index ab2dd3a0f..32b035c9e 100644
--- a/server/tests/misc-endpoints.ts
+++ b/server/tests/misc-endpoints.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts
index 510ec3151..ca57a4b51 100644
--- a/server/tests/plugins/action-hooks.ts
+++ b/server/tests/plugins/action-hooks.ts
@@ -1,6 +1,5 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai'
4import 'mocha' 3import 'mocha'
5import { 4import {
6 cleanupTests, 5 cleanupTests,
@@ -17,18 +16,18 @@ import {
17 createUser, 16 createUser,
18 deleteVideoComment, 17 deleteVideoComment,
19 getPluginTestPath, 18 getPluginTestPath,
20 installPlugin, login, 19 installPlugin,
21 registerUser, removeUser, 20 registerUser,
21 removeUser,
22 setAccessTokensToServers, 22 setAccessTokensToServers,
23 unblockUser, updateUser, 23 unblockUser,
24 updateUser,
24 updateVideo, 25 updateVideo,
25 uploadVideo, 26 uploadVideo,
26 viewVideo, 27 userLogin,
27 userLogin 28 viewVideo
28} from '../../../shared/extra-utils' 29} from '../../../shared/extra-utils'
29 30
30const expect = chai.expect
31
32describe('Test plugin action hooks', function () { 31describe('Test plugin action hooks', function () {
33 let servers: ServerInfo[] 32 let servers: ServerInfo[]
34 let videoUUID: string 33 let videoUUID: string
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts
index 6a5ea4641..6c1fd40ba 100644
--- a/server/tests/plugins/filter-hooks.ts
+++ b/server/tests/plugins/filter-hooks.ts
@@ -1,34 +1,27 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import { cleanupTests, flushAndRunMultipleServers, ServerInfo } from '../../../shared/extra-utils/server/servers'
6 cleanupTests,
7 flushAndRunMultipleServers,
8 flushAndRunServer, killallServers, reRunServer,
9 ServerInfo,
10 waitUntilLog
11} from '../../../shared/extra-utils/server/servers'
12import { 6import {
13 addVideoCommentReply, 7 addVideoCommentReply,
14 addVideoCommentThread, 8 addVideoCommentThread,
15 deleteVideoComment, 9 doubleFollow,
10 getConfig,
16 getPluginTestPath, 11 getPluginTestPath,
17 getVideosList,
18 installPlugin,
19 removeVideo,
20 setAccessTokensToServers,
21 updateVideo,
22 uploadVideo,
23 viewVideo,
24 getVideosListPagination,
25 getVideo, 12 getVideo,
26 getVideoCommentThreads, 13 getVideoCommentThreads,
14 getVideosList,
15 getVideosListPagination,
27 getVideoThreadComments, 16 getVideoThreadComments,
28 getVideoWithToken, 17 getVideoWithToken,
18 installPlugin,
19 registerUser,
20 setAccessTokensToServers,
29 setDefaultVideoChannel, 21 setDefaultVideoChannel,
30 waitJobs, 22 updateVideo,
31 doubleFollow, getConfig, registerUser 23 uploadVideo,
24 waitJobs
32} from '../../../shared/extra-utils' 25} from '../../../shared/extra-utils'
33import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model' 26import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
34import { VideoDetails } from '../../../shared/models/videos' 27import { VideoDetails } from '../../../shared/models/videos'
@@ -140,7 +133,7 @@ describe('Test plugin filter hooks', function () {
140 } 133 }
141 134
142 it('Should blacklist on upload', async function () { 135 it('Should blacklist on upload', async function () {
143 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video please blacklist me' }) 136 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video please blacklist me' })
144 await checkIsBlacklisted(res, true) 137 await checkIsBlacklisted(res, true)
145 }) 138 })
146 139
@@ -157,18 +150,18 @@ describe('Test plugin filter hooks', function () {
157 }) 150 })
158 151
159 it('Should blacklist on update', async function () { 152 it('Should blacklist on update', async function () {
160 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video' }) 153 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' })
161 const videoId = res.body.video.uuid 154 const videoId = res.body.video.uuid
162 await checkIsBlacklisted(res, false) 155 await checkIsBlacklisted(res, false)
163 156
164 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoId, { name: 'please blacklist me' }) 157 await updateVideo(servers[0].url, servers[0].accessToken, videoId, { name: 'please blacklist me' })
165 await checkIsBlacklisted(res, true) 158 await checkIsBlacklisted(res, true)
166 }) 159 })
167 160
168 it('Should blacklist on remote upload', async function () { 161 it('Should blacklist on remote upload', async function () {
169 this.timeout(45000) 162 this.timeout(45000)
170 163
171 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'remote please blacklist me' }) 164 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'remote please blacklist me' })
172 await waitJobs(servers) 165 await waitJobs(servers)
173 166
174 await checkIsBlacklisted(res, true) 167 await checkIsBlacklisted(res, true)
@@ -177,7 +170,7 @@ describe('Test plugin filter hooks', function () {
177 it('Should blacklist on remote update', async function () { 170 it('Should blacklist on remote update', async function () {
178 this.timeout(45000) 171 this.timeout(45000)
179 172
180 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video' }) 173 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video' })
181 await waitJobs(servers) 174 await waitJobs(servers)
182 175
183 const videoId = res.body.video.uuid 176 const videoId = res.body.video.uuid
diff --git a/server/tests/plugins/translations.ts b/server/tests/plugins/translations.ts
index 88d91a033..8dc2043b8 100644
--- a/server/tests/plugins/translations.ts
+++ b/server/tests/plugins/translations.ts
@@ -1,38 +1,15 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers'
5import { 6import {
6 cleanupTests,
7 flushAndRunMultipleServers,
8 flushAndRunServer, killallServers, reRunServer,
9 ServerInfo,
10 waitUntilLog
11} from '../../../shared/extra-utils/server/servers'
12import {
13 addVideoCommentReply,
14 addVideoCommentThread,
15 deleteVideoComment,
16 getPluginTestPath, 7 getPluginTestPath,
17 getVideosList, 8 getPluginTranslations,
18 installPlugin, 9 installPlugin,
19 removeVideo,
20 setAccessTokensToServers, 10 setAccessTokensToServers,
21 updateVideo, 11 uninstallPlugin
22 uploadVideo,
23 viewVideo,
24 getVideosListPagination,
25 getVideo,
26 getVideoCommentThreads,
27 getVideoThreadComments,
28 getVideoWithToken,
29 setDefaultVideoChannel,
30 waitJobs,
31 doubleFollow, getVideoLanguages, getVideoLicences, getVideoCategories, uninstallPlugin, getPluginTranslations
32} from '../../../shared/extra-utils' 12} from '../../../shared/extra-utils'
33import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
34import { VideoDetails } from '../../../shared/models/videos'
35import { getYoutubeVideoUrl, importVideo } from '../../../shared/extra-utils/videos/video-imports'
36 13
37const expect = chai.expect 14const expect = chai.expect
38 15
@@ -69,7 +46,7 @@ describe('Test plugin translations', function () {
69 46
70 expect(res.body).to.deep.equal({ 47 expect(res.body).to.deep.equal({
71 'peertube-plugin-test': { 48 'peertube-plugin-test': {
72 'Hi': 'Coucou' 49 Hi: 'Coucou'
73 }, 50 },
74 'peertube-plugin-test-two': { 51 'peertube-plugin-test-two': {
75 'Hello world': 'Bonjour le monde' 52 'Hello world': 'Bonjour le monde'
@@ -95,7 +72,7 @@ describe('Test plugin translations', function () {
95 72
96 expect(res.body).to.deep.equal({ 73 expect(res.body).to.deep.equal({
97 'peertube-plugin-test': { 74 'peertube-plugin-test': {
98 'Hi': 'Coucou' 75 Hi: 'Coucou'
99 } 76 }
100 }) 77 })
101 } 78 }
diff --git a/server/tests/plugins/video-constants.ts b/server/tests/plugins/video-constants.ts
index 6562e2b45..5374b5ecc 100644
--- a/server/tests/plugins/video-constants.ts
+++ b/server/tests/plugins/video-constants.ts
@@ -1,38 +1,20 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers'
5import { 6import {
6 cleanupTests,
7 flushAndRunMultipleServers,
8 flushAndRunServer, killallServers, reRunServer,
9 ServerInfo,
10 waitUntilLog
11} from '../../../shared/extra-utils/server/servers'
12import {
13 addVideoCommentReply,
14 addVideoCommentThread,
15 deleteVideoComment,
16 getPluginTestPath, 7 getPluginTestPath,
17 getVideosList, 8 getVideo,
9 getVideoCategories,
10 getVideoLanguages,
11 getVideoLicences,
18 installPlugin, 12 installPlugin,
19 removeVideo,
20 setAccessTokensToServers, 13 setAccessTokensToServers,
21 updateVideo, 14 uninstallPlugin,
22 uploadVideo, 15 uploadVideo
23 viewVideo,
24 getVideosListPagination,
25 getVideo,
26 getVideoCommentThreads,
27 getVideoThreadComments,
28 getVideoWithToken,
29 setDefaultVideoChannel,
30 waitJobs,
31 doubleFollow, getVideoLanguages, getVideoLicences, getVideoCategories, uninstallPlugin
32} from '../../../shared/extra-utils' 16} from '../../../shared/extra-utils'
33import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
34import { VideoDetails } from '../../../shared/models/videos' 17import { VideoDetails } from '../../../shared/models/videos'
35import { getYoutubeVideoUrl, importVideo } from '../../../shared/extra-utils/videos/video-imports'
36 18
37const expect = chai.expect 19const expect = chai.expect
38 20
@@ -104,33 +86,33 @@ describe('Test plugin altering video constants', function () {
104 const res = await getVideoLanguages(server.url) 86 const res = await getVideoLanguages(server.url)
105 const languages = res.body 87 const languages = res.body
106 88
107 expect(languages[ 'en' ]).to.equal('English') 89 expect(languages['en']).to.equal('English')
108 expect(languages[ 'fr' ]).to.equal('French') 90 expect(languages['fr']).to.equal('French')
109 91
110 expect(languages[ 'al_bhed' ]).to.not.exist 92 expect(languages['al_bhed']).to.not.exist
111 expect(languages[ 'al_bhed2' ]).to.not.exist 93 expect(languages['al_bhed2']).to.not.exist
112 } 94 }
113 95
114 { 96 {
115 const res = await getVideoCategories(server.url) 97 const res = await getVideoCategories(server.url)
116 const categories = res.body 98 const categories = res.body
117 99
118 expect(categories[ 1 ]).to.equal('Music') 100 expect(categories[1]).to.equal('Music')
119 expect(categories[ 2 ]).to.equal('Films') 101 expect(categories[2]).to.equal('Films')
120 102
121 expect(categories[ 42 ]).to.not.exist 103 expect(categories[42]).to.not.exist
122 expect(categories[ 43 ]).to.not.exist 104 expect(categories[43]).to.not.exist
123 } 105 }
124 106
125 { 107 {
126 const res = await getVideoLicences(server.url) 108 const res = await getVideoLicences(server.url)
127 const licences = res.body 109 const licences = res.body
128 110
129 expect(licences[ 1 ]).to.equal('Attribution') 111 expect(licences[1]).to.equal('Attribution')
130 expect(licences[ 7 ]).to.equal('Public Domain Dedication') 112 expect(licences[7]).to.equal('Public Domain Dedication')
131 113
132 expect(licences[ 42 ]).to.not.exist 114 expect(licences[42]).to.not.exist
133 expect(licences[ 43 ]).to.not.exist 115 expect(licences[43]).to.not.exist
134 } 116 }
135 }) 117 })
136 118
diff --git a/server/tests/real-world/populate-database.ts b/server/tests/real-world/populate-database.ts
deleted file mode 100644
index b1c1688e7..000000000
--- a/server/tests/real-world/populate-database.ts
+++ /dev/null
@@ -1,122 +0,0 @@
1import { VideoRateType } from '../../../shared'
2import {
3 addVideoChannel,
4 createUser,
5 flushTests,
6 getVideosList,
7 killallServers,
8 rateVideo,
9 flushAndRunServer,
10 ServerInfo,
11 setAccessTokensToServers,
12 uploadVideo
13} from '../../../shared/extra-utils'
14import * as Bluebird from 'bluebird'
15
16start()
17 .catch(err => console.error(err))
18
19// ----------------------------------------------------------------------------
20
21async function start () {
22
23 console.log('Flushed tests.')
24
25 const server = await flushAndRunServer(6)
26
27 process.on('exit', async () => {
28 killallServers([ server ])
29 return
30 })
31 process.on('SIGINT', goodbye)
32 process.on('SIGTERM', goodbye)
33
34 await setAccessTokensToServers([ server ])
35
36 console.log('Servers ran.')
37
38 // Forever
39 const fakeTab = Array.from(Array(1000000).keys())
40 const funs = [
41 uploadCustom
42 // uploadCustom,
43 // uploadCustom,
44 // uploadCustom,
45 // likeCustom,
46 // createUserCustom,
47 // createCustomChannel
48 ]
49 const promises = []
50
51 for (const fun of funs) {
52 promises.push(
53 Bluebird.map(fakeTab, () => {
54 return fun(server).catch(err => console.error(err))
55 }, { concurrency: 3 })
56 )
57 }
58
59 await Promise.all(promises)
60}
61
62function getRandomInt (min, max) {
63 return Math.floor(Math.random() * (max - min)) + min
64}
65
66function createCustomChannel (server: ServerInfo) {
67 const videoChannel = {
68 name: Date.now().toString(),
69 displayName: Date.now().toString(),
70 description: Date.now().toString()
71 }
72
73 return addVideoChannel(server.url, server.accessToken, videoChannel)
74}
75
76function createUserCustom (server: ServerInfo) {
77 const username = Date.now().toString() + getRandomInt(0, 100000)
78 console.log('Creating user %s.', username)
79
80 return createUser({ url: server.url, accessToken: server.accessToken, username: username, password: 'coucou' })
81}
82
83function uploadCustom (server: ServerInfo) {
84 console.log('Uploading video.')
85
86 const videoAttributes = {
87 name: Date.now() + ' name',
88 category: 4,
89 nsfw: false,
90 licence: 2,
91 language: 'en',
92 description: Date.now() + ' description',
93 tags: [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ],
94 fixture: 'video_short.mp4'
95 }
96
97 return uploadVideo(server.url, server.accessToken, videoAttributes)
98}
99
100function likeCustom (server: ServerInfo) {
101 return rateCustom(server, 'like')
102}
103
104function dislikeCustom (server: ServerInfo) {
105 return rateCustom(server, 'dislike')
106}
107
108async function rateCustom (server: ServerInfo, rating: VideoRateType) {
109 const res = await getVideosList(server.url)
110
111 const videos = res.body.data
112 if (videos.length === 0) return undefined
113
114 const videoToRate = videos[getRandomInt(0, videos.length)]
115
116 console.log('Rating (%s) video.', rating)
117 return rateVideo(server.url, server.accessToken, videoToRate.id, rating)
118}
119
120function goodbye () {
121 return process.exit(-1)
122}
diff --git a/server/tests/real-world/real-world.ts b/server/tests/real-world/real-world.ts
deleted file mode 100644
index cba5ac311..000000000
--- a/server/tests/real-world/real-world.ts
+++ /dev/null
@@ -1,375 +0,0 @@
1// /!\ Before imports /!\
2process.env.NODE_ENV = 'test'
3
4import * as program from 'commander'
5import { Video, VideoFile, VideoRateType } from '../../../shared'
6import { JobState } from '../../../shared/models'
7import {
8 flushAndRunMultipleServers,
9 flushTests, follow,
10 getVideo,
11 getVideosList, getVideosListPagination,
12 killallServers,
13 removeVideo,
14 ServerInfo as DefaultServerInfo,
15 setAccessTokensToServers,
16 updateVideo,
17 uploadVideo, viewVideo,
18 wait
19} from '../../../shared/extra-utils'
20import { getJobsListPaginationAndSort } from '../../../shared/extra-utils/server/jobs'
21
22interface ServerInfo extends DefaultServerInfo {
23 requestsNumber: number
24}
25
26program
27 .option('-c, --create [weight]', 'Weight for creating videos')
28 .option('-r, --remove [weight]', 'Weight for removing videos')
29 .option('-u, --update [weight]', 'Weight for updating videos')
30 .option('-v, --view [weight]', 'Weight for viewing videos')
31 .option('-l, --like [weight]', 'Weight for liking videos')
32 .option('-s, --dislike [weight]', 'Weight for disliking videos')
33 .option('-p, --servers [n]', 'Number of servers to run (3 or 6)', /^3|6$/, 3)
34 .option('-i, --interval-action [interval]', 'Interval in ms for an action')
35 .option('-I, --interval-integrity [interval]', 'Interval in ms for an integrity check')
36 .option('-f, --flush', 'Flush data on exit')
37 .option('-d, --difference', 'Display difference if integrity is not okay')
38 .parse(process.argv)
39
40const createWeight = program['create'] !== undefined ? parseInt(program['create'], 10) : 5
41const removeWeight = program['remove'] !== undefined ? parseInt(program['remove'], 10) : 4
42const updateWeight = program['update'] !== undefined ? parseInt(program['update'], 10) : 4
43const viewWeight = program['view'] !== undefined ? parseInt(program['view'], 10) : 4
44const likeWeight = program['like'] !== undefined ? parseInt(program['like'], 10) : 4
45const dislikeWeight = program['dislike'] !== undefined ? parseInt(program['dislike'], 10) : 4
46const flushAtExit = program['flush'] || false
47const actionInterval = program['intervalAction'] !== undefined ? parseInt(program['intervalAction'], 10) : 500
48const integrityInterval = program['intervalIntegrity'] !== undefined ? parseInt(program['intervalIntegrity'], 10) : 60000
49const displayDiffOnFail = program['difference'] || false
50
51const numberOfServers = 6
52
53console.log(
54 'Create weight: %d, update weight: %d, remove weight: %d, view weight: %d, like weight: %d, dislike weight: %d.',
55 createWeight, updateWeight, removeWeight, viewWeight, likeWeight, dislikeWeight
56)
57
58if (flushAtExit) {
59 console.log('Program will flush data on exit.')
60} else {
61 console.log('Program will not flush data on exit.')
62}
63if (displayDiffOnFail) {
64 console.log('Program will display diff on failure.')
65} else {
66 console.log('Program will not display diff on failure')
67}
68console.log('Interval in ms for each action: %d.', actionInterval)
69console.log('Interval in ms for each integrity check: %d.', integrityInterval)
70
71console.log('Run servers...')
72
73start()
74
75// ----------------------------------------------------------------------------
76
77async function start () {
78 const servers = await runServers(numberOfServers)
79
80 process.on('exit', async () => {
81 await exitServers(servers, flushAtExit)
82
83 return
84 })
85 process.on('SIGINT', goodbye)
86 process.on('SIGTERM', goodbye)
87
88 console.log('Servers ran')
89 initializeRequestsPerServer(servers)
90
91 let checking = false
92
93 setInterval(async () => {
94 if (checking === true) return
95
96 const rand = getRandomInt(0, createWeight + updateWeight + removeWeight + viewWeight + likeWeight + dislikeWeight)
97
98 const numServer = getRandomNumServer(servers)
99 servers[numServer].requestsNumber++
100
101 if (rand < createWeight) {
102 await upload(servers, numServer)
103 } else if (rand < createWeight + updateWeight) {
104 await update(servers, numServer)
105 } else if (rand < createWeight + updateWeight + removeWeight) {
106 await remove(servers, numServer)
107 } else if (rand < createWeight + updateWeight + removeWeight + viewWeight) {
108 await view(servers, numServer)
109 } else if (rand < createWeight + updateWeight + removeWeight + viewWeight + likeWeight) {
110 await like(servers, numServer)
111 } else {
112 await dislike(servers, numServer)
113 }
114 }, actionInterval)
115
116 // The function will check the consistency between servers (should have the same videos with same attributes...)
117 setInterval(function () {
118 if (checking === true) return
119
120 console.log('Checking integrity...')
121 checking = true
122
123 const waitingInterval = setInterval(async () => {
124 const pendingRequests = await isTherePendingRequests(servers)
125 if (pendingRequests === true) {
126 console.log('A server has pending requests, waiting...')
127 return
128 }
129
130 // Even if there are no pending request, wait some potential processes
131 await wait(2000)
132 await checkIntegrity(servers)
133
134 initializeRequestsPerServer(servers)
135 checking = false
136 clearInterval(waitingInterval)
137 }, 10000)
138 }, integrityInterval)
139}
140
141function initializeRequestsPerServer (servers: ServerInfo[]) {
142 servers.forEach(server => server.requestsNumber = 0)
143}
144
145function getRandomInt (min, max) {
146 return Math.floor(Math.random() * (max - min)) + min
147}
148
149function getRandomNumServer (servers) {
150 return getRandomInt(0, servers.length)
151}
152
153async function runServers (numberOfServers: number) {
154 const servers: ServerInfo[] = (await flushAndRunMultipleServers(numberOfServers))
155 .map(s => Object.assign({ requestsNumber: 0 }, s))
156
157 // Get the access tokens
158 await setAccessTokensToServers(servers)
159
160 for (let i = 0; i < numberOfServers; i++) {
161 for (let j = 0; j < numberOfServers; j++) {
162 if (i === j) continue
163
164 await follow(servers[i].url, [ servers[j].url ], servers[i].accessToken)
165 }
166 }
167
168 return servers
169}
170
171async function exitServers (servers: ServerInfo[], flushAtExit: boolean) {
172 killallServers(servers)
173
174 if (flushAtExit) await flushTests()
175}
176
177function upload (servers: ServerInfo[], numServer: number) {
178 console.log('Uploading video to server ' + numServer)
179
180 const videoAttributes = {
181 name: Date.now() + ' name',
182 category: 4,
183 nsfw: false,
184 licence: 2,
185 language: 'en',
186 description: Date.now() + ' description',
187 tags: [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ],
188 fixture: 'video_short1.webm'
189 }
190 return uploadVideo(servers[numServer].url, servers[numServer].accessToken, videoAttributes)
191}
192
193async function update (servers: ServerInfo[], numServer: number) {
194 const res = await getVideosList(servers[numServer].url)
195
196 const videos = res.body.data.filter(video => video.isLocal === true)
197 if (videos.length === 0) return undefined
198
199 const toUpdate = videos[getRandomInt(0, videos.length)].id
200 const attributes = {
201 name: Date.now() + ' name',
202 description: Date.now() + ' description',
203 tags: [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ]
204 }
205
206 console.log('Updating video of server ' + numServer)
207
208 return updateVideo(servers[numServer].url, servers[numServer].accessToken, toUpdate, attributes)
209}
210
211async function remove (servers: ServerInfo[], numServer: number) {
212 const res = await getVideosList(servers[numServer].url)
213 const videos = res.body.data.filter(video => video.isLocal === true)
214 if (videos.length === 0) return undefined
215
216 const toRemove = videos[getRandomInt(0, videos.length)].id
217
218 console.log('Removing video from server ' + numServer)
219 return removeVideo(servers[numServer].url, servers[numServer].accessToken, toRemove)
220}
221
222async function view (servers: ServerInfo[], numServer: number) {
223 const res = await getVideosList(servers[numServer].url)
224
225 const videos = res.body.data
226 if (videos.length === 0) return undefined
227
228 const toView = videos[getRandomInt(0, videos.length)].id
229
230 console.log('Viewing video from server ' + numServer)
231 return viewVideo(servers[numServer].url, toView)
232}
233
234function like (servers: ServerInfo[], numServer: number) {
235 return rate(servers, numServer, 'like')
236}
237
238function dislike (servers: ServerInfo[], numServer: number) {
239 return rate(servers, numServer, 'dislike')
240}
241
242async function rate (servers: ServerInfo[], numServer: number, rating: VideoRateType) {
243 const res = await getVideosList(servers[numServer].url)
244
245 const videos = res.body.data
246 if (videos.length === 0) return undefined
247
248 const toRate = videos[getRandomInt(0, videos.length)].id
249
250 console.log('Rating (%s) video from server %d', rating, numServer)
251 return getVideo(servers[numServer].url, toRate)
252}
253
254async function checkIntegrity (servers: ServerInfo[]) {
255 const videos: Video[][] = []
256 const tasks: Promise<any>[] = []
257
258 // Fetch all videos and remove some fields that can differ between servers
259 for (const server of servers) {
260 const p = getVideosListPagination(server.url, 0, 1000000, '-createdAt')
261 .then(res => videos.push(res.body.data))
262 tasks.push(p)
263 }
264
265 await Promise.all(tasks)
266
267 let i = 0
268 for (const video of videos) {
269 const differences = areDifferences(video, videos[0])
270 if (differences !== undefined) {
271 console.error('Integrity not ok with server %d!', i + 1)
272
273 if (displayDiffOnFail) {
274 console.log(differences)
275 }
276
277 process.exit(-1)
278 }
279
280 i++
281 }
282
283 console.log('Integrity ok.')
284}
285
286function areDifferences (videos1: Video[], videos2: Video[]) {
287 // Remove some keys we don't want to compare
288 videos1.concat(videos2).forEach(video => {
289 delete video.id
290 delete video.isLocal
291 delete video.thumbnailPath
292 delete video.updatedAt
293 delete video.views
294 })
295
296 if (videos1.length !== videos2.length) {
297 return `Videos length are different (${videos1.length}/${videos2.length}).`
298 }
299
300 for (const video1 of videos1) {
301 const video2 = videos2.find(video => video.uuid === video1.uuid)
302
303 if (!video2) return 'Video ' + video1.uuid + ' is missing.'
304
305 for (const videoKey of Object.keys(video1)) {
306 const attribute1 = video1[videoKey]
307 const attribute2 = video2[videoKey]
308
309 if (videoKey === 'tags') {
310 if (attribute1.length !== attribute2.length) {
311 return 'Tags are different.'
312 }
313
314 attribute1.forEach(tag1 => {
315 if (attribute2.indexOf(tag1) === -1) {
316 return 'Tag ' + tag1 + ' is missing.'
317 }
318 })
319 } else if (videoKey === 'files') {
320 if (attribute1.length !== attribute2.length) {
321 return 'Video files are different.'
322 }
323
324 attribute1.forEach((videoFile1: VideoFile) => {
325 const videoFile2: VideoFile = attribute2.find(videoFile => videoFile.magnetUri === videoFile1.magnetUri)
326 if (!videoFile2) {
327 return `Video ${video1.uuid} has missing video file ${videoFile1.magnetUri}.`
328 }
329
330 if (videoFile1.size !== videoFile2.size || videoFile1.resolution.label !== videoFile2.resolution.label) {
331 return `Video ${video1.uuid} has different video file ${videoFile1.magnetUri}.`
332 }
333 })
334 } else {
335 if (attribute1 !== attribute2) {
336 return `Video ${video1.uuid} has different value for attribute ${videoKey}.`
337 }
338 }
339 }
340 }
341
342 return undefined
343}
344
345function goodbye () {
346 return process.exit(-1)
347}
348
349async function isTherePendingRequests (servers: ServerInfo[]) {
350 const states: JobState[] = [ 'waiting', 'active', 'delayed' ]
351 const tasks: Promise<any>[] = []
352 let pendingRequests = false
353
354 // Check if each server has pending request
355 for (const server of servers) {
356 for (const state of states) {
357 const p = getJobsListPaginationAndSort({
358 url: server.url,
359 accessToken: server.accessToken,
360 state: state,
361 start: 0,
362 count: 10,
363 sort: '-createdAt'
364 })
365 .then(res => {
366 if (res.body.total > 0) pendingRequests = true
367 })
368 tasks.push(p)
369 }
370 }
371
372 await Promise.all(tasks)
373
374 return pendingRequests
375}
diff --git a/server/tools/cli.ts b/server/tools/cli.ts
index 58e2445ac..d1a631b69 100644
--- a/server/tools/cli.ts
+++ b/server/tools/cli.ts
@@ -6,6 +6,9 @@ import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels'
6import { Command } from 'commander' 6import { Command } from 'commander'
7import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' 7import { VideoChannel, VideoPrivacy } from '../../shared/models/videos'
8import { createLogger, format, transports } from 'winston' 8import { createLogger, format, transports } from 'winston'
9import { getMyUserInformation } from '@shared/extra-utils/users/users'
10import { User, UserRole } from '@shared/models'
11import { getAccessToken } from '@shared/extra-utils/users/login'
9 12
10let configName = 'PeerTube/CLI' 13let configName = 'PeerTube/CLI'
11if (isTestInstance()) configName += `-${getAppNumber()}` 14if (isTestInstance()) configName += `-${getAppNumber()}`
@@ -14,8 +17,21 @@ const config = require('application-config')(configName)
14 17
15const version = require('../../../package.json').version 18const version = require('../../../package.json').version
16 19
20async function getAdminTokenOrDie (url: string, username: string, password: string) {
21 const accessToken = await getAccessToken(url, username, password)
22 const resMe = await getMyUserInformation(url, accessToken)
23 const me: User = resMe.body
24
25 if (me.role !== UserRole.ADMINISTRATOR) {
26 console.error('You must be an administrator.')
27 process.exit(-1)
28 }
29
30 return accessToken
31}
32
17interface Settings { 33interface Settings {
18 remotes: any[], 34 remotes: any[]
19 default: number 35 default: number
20} 36}
21 37
@@ -74,9 +90,9 @@ function getRemoteObjectOrDie (
74 if (!program['url'] || !program['username'] || !program['password']) { 90 if (!program['url'] || !program['username'] || !program['password']) {
75 // No remote and we don't have program parameters: quit 91 // No remote and we don't have program parameters: quit
76 if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) { 92 if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) {
77 if (!program[ 'url' ]) console.error('--url field is required.') 93 if (!program['url']) console.error('--url field is required.')
78 if (!program[ 'username' ]) console.error('--username field is required.') 94 if (!program['username']) console.error('--username field is required.')
79 if (!program[ 'password' ]) console.error('--password field is required.') 95 if (!program['password']) console.error('--password field is required.')
80 96
81 return process.exit(-1) 97 return process.exit(-1)
82 } 98 }
@@ -96,9 +112,9 @@ function getRemoteObjectOrDie (
96 } 112 }
97 113
98 return { 114 return {
99 url: program[ 'url' ], 115 url: program['url'],
100 username: program[ 'username' ], 116 username: program['username'],
101 password: program[ 'password' ] 117 password: program['password']
102 } 118 }
103} 119}
104 120
@@ -134,8 +150,8 @@ async function buildVideoAttributesFromCommander (url: string, command: Command,
134 const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {} 150 const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {}
135 151
136 for (const key of Object.keys(defaultBooleanAttributes)) { 152 for (const key of Object.keys(defaultBooleanAttributes)) {
137 if (command[ key ] !== undefined) { 153 if (command[key] !== undefined) {
138 booleanAttributes[key] = command[ key ] 154 booleanAttributes[key] = command[key]
139 } else if (defaultAttributes[key] !== undefined) { 155 } else if (defaultAttributes[key] !== undefined) {
140 booleanAttributes[key] = defaultAttributes[key] 156 booleanAttributes[key] = defaultAttributes[key]
141 } else { 157 } else {
@@ -144,19 +160,19 @@ async function buildVideoAttributesFromCommander (url: string, command: Command,
144 } 160 }
145 161
146 const videoAttributes = { 162 const videoAttributes = {
147 name: command[ 'videoName' ] || defaultAttributes.name, 163 name: command['videoName'] || defaultAttributes.name,
148 category: command[ 'category' ] || defaultAttributes.category || undefined, 164 category: command['category'] || defaultAttributes.category || undefined,
149 licence: command[ 'licence' ] || defaultAttributes.licence || undefined, 165 licence: command['licence'] || defaultAttributes.licence || undefined,
150 language: command[ 'language' ] || defaultAttributes.language || undefined, 166 language: command['language'] || defaultAttributes.language || undefined,
151 privacy: command[ 'privacy' ] || defaultAttributes.privacy || VideoPrivacy.PUBLIC, 167 privacy: command['privacy'] || defaultAttributes.privacy || VideoPrivacy.PUBLIC,
152 support: command[ 'support' ] || defaultAttributes.support || undefined, 168 support: command['support'] || defaultAttributes.support || undefined,
153 description: command[ 'videoDescription' ] || defaultAttributes.description || undefined, 169 description: command['videoDescription'] || defaultAttributes.description || undefined,
154 tags: command[ 'tags' ] || defaultAttributes.tags || undefined 170 tags: command['tags'] || defaultAttributes.tags || undefined
155 } 171 }
156 172
157 Object.assign(videoAttributes, booleanAttributes) 173 Object.assign(videoAttributes, booleanAttributes)
158 174
159 if (command[ 'channelName' ]) { 175 if (command['channelName']) {
160 const res = await getVideoChannel(url, command['channelName']) 176 const res = await getVideoChannel(url, command['channelName'])
161 const videoChannel: VideoChannel = res.body 177 const videoChannel: VideoChannel = res.body
162 178
@@ -172,9 +188,9 @@ async function buildVideoAttributesFromCommander (url: string, command: Command,
172 188
173function getServerCredentials (program: any) { 189function getServerCredentials (program: any) {
174 return Promise.all([ getSettings(), getNetrc() ]) 190 return Promise.all([ getSettings(), getNetrc() ])
175 .then(([ settings, netrc ]) => { 191 .then(([ settings, netrc ]) => {
176 return getRemoteObjectOrDie(program, settings, netrc) 192 return getRemoteObjectOrDie(program, settings, netrc)
177 }) 193 })
178} 194}
179 195
180function getLogger (logLevel = 'info') { 196function getLogger (logLevel = 'info') {
@@ -222,5 +238,7 @@ export {
222 getServerCredentials, 238 getServerCredentials,
223 239
224 buildCommonVideoOptions, 240 buildCommonVideoOptions,
225 buildVideoAttributesFromCommander 241 buildVideoAttributesFromCommander,
242
243 getAdminTokenOrDie
226} 244}
diff --git a/server/tools/package.json b/server/tools/package.json
index 40959d76e..06ad31cab 100644
--- a/server/tools/package.json
+++ b/server/tools/package.json
@@ -4,11 +4,12 @@
4 "private": true, 4 "private": true,
5 "dependencies": { 5 "dependencies": {
6 "application-config": "^1.0.1", 6 "application-config": "^1.0.1",
7 "cli-table": "^0.3.1", 7 "cli-table3": "^0.5.1",
8 "netrc-parser": "^3.1.6", 8 "netrc-parser": "^3.1.6",
9 "webtorrent-hybrid": "^4.0.1" 9 "webtorrent-hybrid": "^4.0.1"
10 }, 10 },
11 "summon": { 11 "summon": {
12 "silent": true 12 "silent": true
13 } 13 },
14 "devDependencies": {}
14} 15}
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts
index 6597a5c36..6b486e575 100644
--- a/server/tools/peertube-auth.ts
+++ b/server/tools/peertube-auth.ts
@@ -1,3 +1,5 @@
1// eslint-disable @typescript-eslint/no-unnecessary-type-assertion
2
1import { registerTSPaths } from '../helpers/register-ts-paths' 3import { registerTSPaths } from '../helpers/register-ts-paths'
2registerTSPaths() 4registerTSPaths()
3 5
@@ -5,9 +7,8 @@ import * as program from 'commander'
5import * as prompt from 'prompt' 7import * as prompt from 'prompt'
6import { getNetrc, getSettings, writeSettings } from './cli' 8import { getNetrc, getSettings, writeSettings } from './cli'
7import { isUserUsernameValid } from '../helpers/custom-validators/users' 9import { isUserUsernameValid } from '../helpers/custom-validators/users'
8import { getAccessToken, login } from '../../shared/extra-utils' 10import { getAccessToken } from '../../shared/extra-utils'
9 11import * as CliTable3 from 'cli-table3'
10const Table = require('cli-table')
11 12
12async function delInstance (url: string) { 13async function delInstance (url: string) {
13 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) 14 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ])
@@ -108,10 +109,10 @@ program
108 .action(async () => { 109 .action(async () => {
109 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) 110 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ])
110 111
111 const table = new Table({ 112 const table = new CliTable3({
112 head: ['instance', 'login'], 113 head: [ 'instance', 'login' ],
113 colWidths: [30, 30] 114 colWidths: [ 30, 30 ]
114 }) 115 }) as any
115 116
116 settings.remotes.forEach(element => { 117 settings.remotes.forEach(element => {
117 if (!netrc.machines[element]) return 118 if (!netrc.machines[element]) return
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts
index eaa792763..0efe87810 100644
--- a/server/tools/peertube-import-videos.ts
+++ b/server/tools/peertube-import-videos.ts
@@ -1,10 +1,6 @@
1import { registerTSPaths } from '../helpers/register-ts-paths' 1import { registerTSPaths } from '../helpers/register-ts-paths'
2
3registerTSPaths() 2registerTSPaths()
4 3
5// FIXME: https://github.com/nodejs/node/pull/16853
6require('tls').DEFAULT_ECDH_CURVE = 'auto'
7
8import * as program from 'commander' 4import * as program from 'commander'
9import { join } from 'path' 5import { join } from 'path'
10import { doRequestAndSaveToFile } from '../helpers/requests' 6import { doRequestAndSaveToFile } from '../helpers/requests'
@@ -16,7 +12,7 @@ import { accessSync, constants } from 'fs'
16import { remove } from 'fs-extra' 12import { remove } from 'fs-extra'
17import { sha256 } from '../helpers/core-utils' 13import { sha256 } from '../helpers/core-utils'
18import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' 14import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl'
19import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials, getLogger } from './cli' 15import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getLogger, getServerCredentials } from './cli'
20 16
21type UserInfo = { 17type UserInfo = {
22 username: string 18 username: string
@@ -44,30 +40,29 @@ command
44 .option('-T, --tmpdir <tmpdir>', 'Working directory', __dirname) 40 .option('-T, --tmpdir <tmpdir>', 'Working directory', __dirname)
45 .parse(process.argv) 41 .parse(process.argv)
46 42
47let log = getLogger(program[ 'verbose' ]) 43const log = getLogger(program['verbose'])
48 44
49getServerCredentials(command) 45getServerCredentials(command)
50 .then(({ url, username, password }) => { 46 .then(({ url, username, password }) => {
51 if (!program[ 'targetUrl' ]) { 47 if (!program['targetUrl']) {
52 exitError('--target-url field is required.') 48 exitError('--target-url field is required.')
53 } 49 }
54 50
55 try { 51 try {
56 accessSync(program[ 'tmpdir' ], constants.R_OK | constants.W_OK) 52 accessSync(program['tmpdir'], constants.R_OK | constants.W_OK)
57 } catch (e) { 53 } catch (e) {
58 exitError('--tmpdir %s: directory does not exist or is not accessible', program[ 'tmpdir' ]) 54 exitError('--tmpdir %s: directory does not exist or is not accessible', program['tmpdir'])
59 } 55 }
60 56
61 url = normalizeTargetUrl(url) 57 url = normalizeTargetUrl(url)
62 program[ 'targetUrl' ] = normalizeTargetUrl(program[ 'targetUrl' ]) 58 program['targetUrl'] = normalizeTargetUrl(program['targetUrl'])
63 59
64 const user = { username, password } 60 const user = { username, password }
65 61
66 run(url, user) 62 run(url, user)
67 .catch(err => { 63 .catch(err => exitError(err))
68 exitError(err)
69 })
70 }) 64 })
65 .catch(err => console.error(err))
71 66
72async function run (url: string, user: UserInfo) { 67async function run (url: string, user: UserInfo) {
73 if (!user.password) { 68 if (!user.password) {
@@ -77,7 +72,7 @@ async function run (url: string, user: UserInfo) {
77 const youtubeDL = await safeGetYoutubeDL() 72 const youtubeDL = await safeGetYoutubeDL()
78 73
79 const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] 74 const options = [ '-j', '--flat-playlist', '--playlist-reverse' ]
80 youtubeDL.getInfo(program[ 'targetUrl' ], options, processOptions, async (err, info) => { 75 youtubeDL.getInfo(program['targetUrl'], options, processOptions, async (err, info) => {
81 if (err) { 76 if (err) {
82 exitError(err.message) 77 exitError(err.message)
83 } 78 }
@@ -86,10 +81,10 @@ async function run (url: string, user: UserInfo) {
86 81
87 // Normalize utf8 fields 82 // Normalize utf8 fields
88 infoArray = [].concat(info) 83 infoArray = [].concat(info)
89 if (program[ 'first' ]) { 84 if (program['first']) {
90 infoArray = infoArray.slice(0, program[ 'first' ]) 85 infoArray = infoArray.slice(0, program['first'])
91 } else if (program[ 'last' ]) { 86 } else if (program['last']) {
92 infoArray = infoArray.slice(-program[ 'last' ]) 87 infoArray = infoArray.slice(-program['last'])
93 } 88 }
94 infoArray = infoArray.map(i => normalizeObject(i)) 89 infoArray = infoArray.map(i => normalizeObject(i))
95 90
@@ -97,22 +92,22 @@ async function run (url: string, user: UserInfo) {
97 92
98 for (const info of infoArray) { 93 for (const info of infoArray) {
99 await processVideo({ 94 await processVideo({
100 cwd: program[ 'tmpdir' ], 95 cwd: program['tmpdir'],
101 url, 96 url,
102 user, 97 user,
103 youtubeInfo: info 98 youtubeInfo: info
104 }) 99 })
105 } 100 }
106 101
107 log.info('Video/s for user %s imported: %s', user.username, program[ 'targetUrl' ]) 102 log.info('Video/s for user %s imported: %s', user.username, program['targetUrl'])
108 process.exit(0) 103 process.exit(0)
109 }) 104 })
110} 105}
111 106
112function processVideo (parameters: { 107function processVideo (parameters: {
113 cwd: string, 108 cwd: string
114 url: string, 109 url: string
115 user: { username: string, password: string }, 110 user: { username: string, password: string }
116 youtubeInfo: any 111 youtubeInfo: any
117}) { 112}) {
118 const { youtubeInfo, cwd, url, user } = parameters 113 const { youtubeInfo, cwd, url, user } = parameters
@@ -123,17 +118,17 @@ function processVideo (parameters: {
123 const videoInfo = await fetchObject(youtubeInfo) 118 const videoInfo = await fetchObject(youtubeInfo)
124 log.debug('Fetched object.', videoInfo) 119 log.debug('Fetched object.', videoInfo)
125 120
126 if (program[ 'since' ]) { 121 if (program['since']) {
127 if (buildOriginallyPublishedAt(videoInfo).getTime() < program[ 'since' ].getTime()) { 122 if (buildOriginallyPublishedAt(videoInfo).getTime() < program['since'].getTime()) {
128 log.info('Video "%s" has been published before "%s", don\'t upload it.\n', 123 log.info('Video "%s" has been published before "%s", don\'t upload it.\n',
129 videoInfo.title, formatDate(program[ 'since' ])) 124 videoInfo.title, formatDate(program['since']))
130 return res() 125 return res()
131 } 126 }
132 } 127 }
133 if (program[ 'until' ]) { 128 if (program['until']) {
134 if (buildOriginallyPublishedAt(videoInfo).getTime() > program[ 'until' ].getTime()) { 129 if (buildOriginallyPublishedAt(videoInfo).getTime() > program['until'].getTime()) {
135 log.info('Video "%s" has been published after "%s", don\'t upload it.\n', 130 log.info('Video "%s" has been published after "%s", don\'t upload it.\n',
136 videoInfo.title, formatDate(program[ 'until' ])) 131 videoInfo.title, formatDate(program['until']))
137 return res() 132 return res()
138 } 133 }
139 } 134 }
@@ -178,11 +173,11 @@ function processVideo (parameters: {
178} 173}
179 174
180async function uploadVideoOnPeerTube (parameters: { 175async function uploadVideoOnPeerTube (parameters: {
181 videoInfo: any, 176 videoInfo: any
182 videoPath: string, 177 videoPath: string
183 cwd: string, 178 cwd: string
184 url: string, 179 url: string
185 user: { username: string; password: string } 180 user: { username: string, password: string }
186}) { 181}) {
187 const { videoInfo, videoPath, cwd, url, user } = parameters 182 const { videoInfo, videoPath, cwd, url, user } = parameters
188 183
@@ -210,9 +205,9 @@ async function uploadVideoOnPeerTube (parameters: {
210 205
211 const defaultAttributes = { 206 const defaultAttributes = {
212 name: truncate(videoInfo.title, { 207 name: truncate(videoInfo.title, {
213 'length': CONSTRAINTS_FIELDS.VIDEOS.NAME.max, 208 length: CONSTRAINTS_FIELDS.VIDEOS.NAME.max,
214 'separator': /,? +/, 209 separator: /,? +/,
215 'omission': ' […]' 210 omission: ' […]'
216 }), 211 }),
217 category, 212 category,
218 licence, 213 licence,
@@ -259,7 +254,7 @@ async function uploadVideoOnPeerTube (parameters: {
259async function getCategory (categories: string[], url: string) { 254async function getCategory (categories: string[], url: string) {
260 if (!categories) return undefined 255 if (!categories) return undefined
261 256
262 const categoryString = categories[ 0 ] 257 const categoryString = categories[0]
263 258
264 if (categoryString === 'News & Politics') return 11 259 if (categoryString === 'News & Politics') return 11
265 260
@@ -267,7 +262,7 @@ async function getCategory (categories: string[], url: string) {
267 const categoriesServer = res.body 262 const categoriesServer = res.body
268 263
269 for (const key of Object.keys(categoriesServer)) { 264 for (const key of Object.keys(categoriesServer)) {
270 const categoryServer = categoriesServer[ key ] 265 const categoryServer = categoriesServer[key]
271 if (categoryString.toLowerCase() === categoryServer.toLowerCase()) return parseInt(key, 10) 266 if (categoryString.toLowerCase() === categoryServer.toLowerCase()) return parseInt(key, 10)
272 } 267 }
273 268
@@ -289,12 +284,12 @@ function normalizeObject (obj: any) {
289 // Deprecated key 284 // Deprecated key
290 if (key === 'resolution') continue 285 if (key === 'resolution') continue
291 286
292 const value = obj[ key ] 287 const value = obj[key]
293 288
294 if (typeof value === 'string') { 289 if (typeof value === 'string') {
295 newObj[ key ] = value.normalize() 290 newObj[key] = value.normalize()
296 } else { 291 } else {
297 newObj[ key ] = value 292 newObj[key] = value
298 } 293 }
299 } 294 }
300 295
@@ -306,7 +301,7 @@ function fetchObject (info: any) {
306 301
307 return new Promise<any>(async (res, rej) => { 302 return new Promise<any>(async (res, rej) => {
308 const youtubeDL = await safeGetYoutubeDL() 303 const youtubeDL = await safeGetYoutubeDL()
309 youtubeDL.getInfo(url, undefined, processOptions, async (err, videoInfo) => { 304 youtubeDL.getInfo(url, undefined, processOptions, (err, videoInfo) => {
310 if (err) return rej(err) 305 if (err) return rej(err)
311 306
312 const videoInfoWithUrl = Object.assign(videoInfo, { url }) 307 const videoInfoWithUrl = Object.assign(videoInfo, { url })
@@ -317,10 +312,10 @@ function fetchObject (info: any) {
317 312
318function buildUrl (info: any) { 313function buildUrl (info: any) {
319 const webpageUrl = info.webpage_url as string 314 const webpageUrl = info.webpage_url as string
320 if (webpageUrl && webpageUrl.match(/^https?:\/\//)) return webpageUrl 315 if (webpageUrl?.match(/^https?:\/\//)) return webpageUrl
321 316
322 const url = info.url as string 317 const url = info.url as string
323 if (url && url.match(/^https?:\/\//)) return url 318 if (url?.match(/^https?:\/\//)) return url
324 319
325 // It seems youtube-dl does not return the video url 320 // It seems youtube-dl does not return the video url
326 return 'https://www.youtube.com/watch?v=' + info.id 321 return 'https://www.youtube.com/watch?v=' + info.id
@@ -388,7 +383,7 @@ function parseDate (dateAsStr: string): Date {
388} 383}
389 384
390function formatDate (date: Date): string { 385function formatDate (date: Date): string {
391 return date.toISOString().split('T')[ 0 ] 386 return date.toISOString().split('T')[0]
392} 387}
393 388
394function exitError (message: string, ...meta: any[]) { 389function exitError (message: string, ...meta: any[]) {
diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts
index e40606107..05b75fab2 100644
--- a/server/tools/peertube-plugins.ts
+++ b/server/tools/peertube-plugins.ts
@@ -1,17 +1,15 @@
1// eslint-disable @typescript-eslint/no-unnecessary-type-assertion
2
1import { registerTSPaths } from '../helpers/register-ts-paths' 3import { registerTSPaths } from '../helpers/register-ts-paths'
2registerTSPaths() 4registerTSPaths()
3 5
4import * as program from 'commander' 6import * as program from 'commander'
5import { PluginType } from '../../shared/models/plugins/plugin.type' 7import { PluginType } from '../../shared/models/plugins/plugin.type'
6import { getAccessToken } from '../../shared/extra-utils/users/login'
7import { getMyUserInformation } from '../../shared/extra-utils/users/users'
8import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins' 8import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins'
9import { getServerCredentials } from './cli' 9import { getAdminTokenOrDie, getServerCredentials } from './cli'
10import { User, UserRole } from '../../shared/models/users'
11import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model' 10import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model'
12import { isAbsolute } from 'path' 11import { isAbsolute } from 'path'
13 12import * as CliTable3 from 'cli-table3'
14const Table = require('cli-table')
15 13
16program 14program
17 .name('plugins') 15 .name('plugins')
@@ -82,10 +80,10 @@ async function pluginsListCLI () {
82 }) 80 })
83 const plugins: PeerTubePlugin[] = res.body.data 81 const plugins: PeerTubePlugin[] = res.body.data
84 82
85 const table = new Table({ 83 const table = new CliTable3({
86 head: ['name', 'version', 'homepage'], 84 head: [ 'name', 'version', 'homepage' ],
87 colWidths: [ 50, 10, 50 ] 85 colWidths: [ 50, 10, 50 ]
88 }) 86 }) as any
89 87
90 for (const plugin of plugins) { 88 for (const plugin of plugins) {
91 const npmName = plugin.type === PluginType.PLUGIN 89 const npmName = plugin.type === PluginType.PLUGIN
@@ -128,7 +126,6 @@ async function installPluginCLI (options: any) {
128 } catch (err) { 126 } catch (err) {
129 console.error('Cannot install plugin.', err) 127 console.error('Cannot install plugin.', err)
130 process.exit(-1) 128 process.exit(-1)
131 return
132 } 129 }
133 130
134 console.log('Plugin installed.') 131 console.log('Plugin installed.')
@@ -160,7 +157,6 @@ async function updatePluginCLI (options: any) {
160 } catch (err) { 157 } catch (err) {
161 console.error('Cannot update plugin.', err) 158 console.error('Cannot update plugin.', err)
162 process.exit(-1) 159 process.exit(-1)
163 return
164 } 160 }
165 161
166 console.log('Plugin updated.') 162 console.log('Plugin updated.')
@@ -181,27 +177,13 @@ async function uninstallPluginCLI (options: any) {
181 await uninstallPlugin({ 177 await uninstallPlugin({
182 url, 178 url,
183 accessToken, 179 accessToken,
184 npmName: options[ 'npmName' ] 180 npmName: options['npmName']
185 }) 181 })
186 } catch (err) { 182 } catch (err) {
187 console.error('Cannot uninstall plugin.', err) 183 console.error('Cannot uninstall plugin.', err)
188 process.exit(-1) 184 process.exit(-1)
189 return
190 } 185 }
191 186
192 console.log('Plugin uninstalled.') 187 console.log('Plugin uninstalled.')
193 process.exit(0) 188 process.exit(0)
194} 189}
195
196async function getAdminTokenOrDie (url: string, username: string, password: string) {
197 const accessToken = await getAccessToken(url, username, password)
198 const resMe = await getMyUserInformation(url, accessToken)
199 const me: User = resMe.body
200
201 if (me.role !== UserRole.ADMINISTRATOR) {
202 console.error('Cannot list plugins if you are not administrator.')
203 process.exit(-1)
204 }
205
206 return accessToken
207}
diff --git a/server/tools/peertube-redundancy.ts b/server/tools/peertube-redundancy.ts
new file mode 100644
index 000000000..1ab58a438
--- /dev/null
+++ b/server/tools/peertube-redundancy.ts
@@ -0,0 +1,197 @@
1// eslint-disable @typescript-eslint/no-unnecessary-type-assertion
2
3import { registerTSPaths } from '../helpers/register-ts-paths'
4registerTSPaths()
5
6import * as program from 'commander'
7import { getAdminTokenOrDie, getServerCredentials } from './cli'
8import { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models'
9import { addVideoRedundancy, listVideoRedundancies, removeVideoRedundancy } from '@shared/extra-utils/server/redundancy'
10import validator from 'validator'
11import * as CliTable3 from 'cli-table3'
12import { URL } from 'url'
13import { uniq } from 'lodash'
14
15import bytes = require('bytes')
16
17program
18 .name('plugins')
19 .usage('[command] [options]')
20
21program
22 .command('list-remote-redundancies')
23 .description('List remote redundancies on your videos')
24 .option('-u, --url <url>', 'Server url')
25 .option('-U, --username <username>', 'Username')
26 .option('-p, --password <token>', 'Password')
27 .action(() => listRedundanciesCLI('my-videos'))
28
29program
30 .command('list-my-redundancies')
31 .description('List your redundancies of remote videos')
32 .option('-u, --url <url>', 'Server url')
33 .option('-U, --username <username>', 'Username')
34 .option('-p, --password <token>', 'Password')
35 .action(() => listRedundanciesCLI('remote-videos'))
36
37program
38 .command('add')
39 .description('Duplicate a video in your redundancy system')
40 .option('-u, --url <url>', 'Server url')
41 .option('-U, --username <username>', 'Username')
42 .option('-p, --password <token>', 'Password')
43 .option('-v, --video <videoId>', 'Video id to duplicate')
44 .action((options) => addRedundancyCLI(options))
45
46program
47 .command('remove')
48 .description('Remove a video from your redundancies')
49 .option('-u, --url <url>', 'Server url')
50 .option('-U, --username <username>', 'Username')
51 .option('-p, --password <token>', 'Password')
52 .option('-v, --video <videoId>', 'Video id to remove from redundancies')
53 .action((options) => removeRedundancyCLI(options))
54
55if (!process.argv.slice(2).length) {
56 program.outputHelp()
57}
58
59program.parse(process.argv)
60
61// ----------------------------------------------------------------------------
62
63async function listRedundanciesCLI (target: VideoRedundanciesTarget) {
64 const { url, username, password } = await getServerCredentials(program)
65 const accessToken = await getAdminTokenOrDie(url, username, password)
66
67 const redundancies = await listVideoRedundanciesData(url, accessToken, target)
68
69 const table = new CliTable3({
70 head: [ 'video id', 'video name', 'video url', 'files', 'playlists', 'by instances', 'total size' ]
71 }) as any
72
73 for (const redundancy of redundancies) {
74 const webtorrentFiles = redundancy.redundancies.files
75 const streamingPlaylists = redundancy.redundancies.streamingPlaylists
76
77 let totalSize = ''
78 if (target === 'remote-videos') {
79 const tmp = webtorrentFiles.concat(streamingPlaylists)
80 .reduce((a, b) => a + b.size, 0)
81
82 totalSize = bytes(tmp)
83 }
84
85 const instances = uniq(
86 webtorrentFiles.concat(streamingPlaylists)
87 .map(r => r.fileUrl)
88 .map(u => new URL(u).host)
89 )
90
91 table.push([
92 redundancy.id.toString(),
93 redundancy.name,
94 redundancy.url,
95 webtorrentFiles.length,
96 streamingPlaylists.length,
97 instances.join('\n'),
98 totalSize
99 ])
100 }
101
102 console.log(table.toString())
103 process.exit(0)
104}
105
106async function addRedundancyCLI (options: { videoId: number }) {
107 const { url, username, password } = await getServerCredentials(program)
108 const accessToken = await getAdminTokenOrDie(url, username, password)
109
110 if (!options['video'] || validator.isInt('' + options['video']) === false) {
111 console.error('You need to specify the video id to duplicate and it should be a number.\n')
112 program.outputHelp()
113 process.exit(-1)
114 }
115
116 try {
117 await addVideoRedundancy({
118 url,
119 accessToken,
120 videoId: options['video']
121 })
122
123 console.log('Video will be duplicated by your instance!')
124
125 process.exit(0)
126 } catch (err) {
127 if (err.message.includes(409)) {
128 console.error('This video is already duplicated by your instance.')
129 } else if (err.message.includes(404)) {
130 console.error('This video id does not exist.')
131 } else {
132 console.error(err)
133 }
134
135 process.exit(-1)
136 }
137}
138
139async function removeRedundancyCLI (options: { videoId: number }) {
140 const { url, username, password } = await getServerCredentials(program)
141 const accessToken = await getAdminTokenOrDie(url, username, password)
142
143 if (!options['video'] || validator.isInt('' + options['video']) === false) {
144 console.error('You need to specify the video id to remove from your redundancies.\n')
145 program.outputHelp()
146 process.exit(-1)
147 }
148
149 const videoId = parseInt(options['video'] + '', 10)
150
151 let redundancies = await listVideoRedundanciesData(url, accessToken, 'my-videos')
152 let videoRedundancy = redundancies.find(r => videoId === r.id)
153
154 if (!videoRedundancy) {
155 redundancies = await listVideoRedundanciesData(url, accessToken, 'remote-videos')
156 videoRedundancy = redundancies.find(r => videoId === r.id)
157 }
158
159 if (!videoRedundancy) {
160 console.error('Video redundancy not found.')
161 process.exit(-1)
162 }
163
164 try {
165 const ids = videoRedundancy.redundancies.files
166 .concat(videoRedundancy.redundancies.streamingPlaylists)
167 .map(r => r.id)
168
169 for (const id of ids) {
170 await removeVideoRedundancy({
171 url,
172 accessToken,
173 redundancyId: id
174 })
175 }
176
177 console.log('Video redundancy removed!')
178
179 process.exit(0)
180 } catch (err) {
181 console.error(err)
182 process.exit(-1)
183 }
184}
185
186async function listVideoRedundanciesData (url: string, accessToken: string, target: VideoRedundanciesTarget) {
187 const res = await listVideoRedundancies({
188 url,
189 accessToken,
190 start: 0,
191 count: 100,
192 sort: 'name',
193 target
194 })
195
196 return res.body.data as VideoRedundancy[]
197}
diff --git a/server/tools/peertube-repl.ts b/server/tools/peertube-repl.ts
index ab6e215d9..7c936ae0d 100644
--- a/server/tools/peertube-repl.ts
+++ b/server/tools/peertube-repl.ts
@@ -1,6 +1,4 @@
1import { registerTSPaths } from '../helpers/register-ts-paths' 1import { registerTSPaths } from '../helpers/register-ts-paths'
2registerTSPaths()
3
4import * as repl from 'repl' 2import * as repl from 'repl'
5import * as path from 'path' 3import * as path from 'path'
6import * as _ from 'lodash' 4import * as _ from 'lodash'
@@ -23,6 +21,8 @@ import * as signupUtils from '../helpers/signup'
23import * as utils from '../helpers/utils' 21import * as utils from '../helpers/utils'
24import * as YoutubeDLUtils from '../helpers/youtube-dl' 22import * as YoutubeDLUtils from '../helpers/youtube-dl'
25 23
24registerTSPaths()
25
26const start = async () => { 26const start = async () => {
27 await initDatabaseModels(true) 27 await initDatabaseModels(true)
28 28
@@ -31,22 +31,39 @@ const start = async () => {
31 const initContext = (replServer) => { 31 const initContext = (replServer) => {
32 return (context) => { 32 return (context) => {
33 const properties = { 33 const properties = {
34 context, repl: replServer, env: process.env, 34 context,
35 lodash: _, path, 35 repl: replServer,
36 uuidv1, uuidv3, uuidv4, uuidv5, 36 env: process.env,
37 cli, logger, constants, 37 lodash: _,
38 Sequelize, sequelizeTypescript, modelsUtils, 38 path,
39 models: sequelizeTypescript.models, transaction: sequelizeTypescript.transaction, 39 uuidv1,
40 query: sequelizeTypescript.query, queryInterface: sequelizeTypescript.getQueryInterface(), 40 uuidv3,
41 uuidv4,
42 uuidv5,
43 cli,
44 logger,
45 constants,
46 Sequelize,
47 sequelizeTypescript,
48 modelsUtils,
49 models: sequelizeTypescript.models,
50 transaction: sequelizeTypescript.transaction,
51 query: sequelizeTypescript.query,
52 queryInterface: sequelizeTypescript.getQueryInterface(),
41 YoutubeDL, 53 YoutubeDL,
42 coreUtils, ffmpegUtils, peertubeCryptoUtils, signupUtils, utils, YoutubeDLUtils 54 coreUtils,
55 ffmpegUtils,
56 peertubeCryptoUtils,
57 signupUtils,
58 utils,
59 YoutubeDLUtils
43 } 60 }
44 61
45 for (let prop in properties) { 62 for (const prop in properties) {
46 Object.defineProperty(context, prop, { 63 Object.defineProperty(context, prop, {
47 configurable: false, 64 configurable: false,
48 enumerable: true, 65 enumerable: true,
49 value: properties[ prop ] 66 value: properties[prop]
50 }) 67 })
51 } 68 }
52 } 69 }
diff --git a/server/tools/peertube-upload.ts b/server/tools/peertube-upload.ts
index f604c9bee..8de952e7b 100644
--- a/server/tools/peertube-upload.ts
+++ b/server/tools/peertube-upload.ts
@@ -24,14 +24,14 @@ command
24 24
25getServerCredentials(command) 25getServerCredentials(command)
26 .then(({ url, username, password }) => { 26 .then(({ url, username, password }) => {
27 if (!program[ 'videoName' ] || !program[ 'file' ]) { 27 if (!program['videoName'] || !program['file']) {
28 if (!program[ 'videoName' ]) console.error('--video-name is required.') 28 if (!program['videoName']) console.error('--video-name is required.')
29 if (!program[ 'file' ]) console.error('--file is required.') 29 if (!program['file']) console.error('--file is required.')
30 30
31 process.exit(-1) 31 process.exit(-1)
32 } 32 }
33 33
34 if (isAbsolute(program[ 'file' ]) === false) { 34 if (isAbsolute(program['file']) === false) {
35 console.error('File path should be absolute.') 35 console.error('File path should be absolute.')
36 process.exit(-1) 36 process.exit(-1)
37 } 37 }
@@ -41,25 +41,26 @@ getServerCredentials(command)
41 process.exit(-1) 41 process.exit(-1)
42 }) 42 })
43 }) 43 })
44 .catch(err => console.error(err))
44 45
45async function run (url: string, username: string, password: string) { 46async function run (url: string, username: string, password: string) {
46 const accessToken = await getAccessToken(url, username, password) 47 const accessToken = await getAccessToken(url, username, password)
47 48
48 await access(program[ 'file' ], constants.F_OK) 49 await access(program['file'], constants.F_OK)
49 50
50 console.log('Uploading %s video...', program[ 'videoName' ]) 51 console.log('Uploading %s video...', program['videoName'])
51 52
52 const videoAttributes = await buildVideoAttributesFromCommander(url, program) 53 const videoAttributes = await buildVideoAttributesFromCommander(url, program)
53 54
54 Object.assign(videoAttributes, { 55 Object.assign(videoAttributes, {
55 fixture: program[ 'file' ], 56 fixture: program['file'],
56 thumbnailfile: program[ 'thumbnail' ], 57 thumbnailfile: program['thumbnail'],
57 previewfile: program[ 'preview' ] 58 previewfile: program['preview']
58 }) 59 })
59 60
60 try { 61 try {
61 await uploadVideo(url, accessToken, videoAttributes) 62 await uploadVideo(url, accessToken, videoAttributes)
62 console.log(`Video ${program[ 'videoName' ]} uploaded.`) 63 console.log(`Video ${program['videoName']} uploaded.`)
63 process.exit(0) 64 process.exit(0)
64 } catch (err) { 65 } catch (err) {
65 console.error(require('util').inspect(err)) 66 console.error(require('util').inspect(err))
diff --git a/server/tools/peertube-watch.ts b/server/tools/peertube-watch.ts
index 9ac1d05f9..5f7d1bb07 100644
--- a/server/tools/peertube-watch.ts
+++ b/server/tools/peertube-watch.ts
@@ -29,16 +29,10 @@ program
29 console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10') 29 console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10')
30 console.log() 30 console.log()
31 }) 31 })
32 .action((url, cmd) => { 32 .action((url, cmd) => run(url, cmd))
33 run(url, cmd)
34 .catch(err => {
35 console.error(err)
36 process.exit(-1)
37 })
38 })
39 .parse(process.argv) 33 .parse(process.argv)
40 34
41async function run (url: string, program: any) { 35function run (url: string, program: any) {
42 if (!url) { 36 if (!url) {
43 console.error('<url> positional argument is required.') 37 console.error('<url> positional argument is required.')
44 process.exit(-1) 38 process.exit(-1)
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts
index fc85c4210..88dd5f7f6 100644
--- a/server/tools/peertube.ts
+++ b/server/tools/peertube.ts
@@ -1,13 +1,12 @@
1#!/usr/bin/env node 1#!/usr/bin/env node
2 2
3/* eslint-disable no-useless-escape */
4
3import { registerTSPaths } from '../helpers/register-ts-paths' 5import { registerTSPaths } from '../helpers/register-ts-paths'
4registerTSPaths() 6registerTSPaths()
5 7
6import * as program from 'commander' 8import * as program from 'commander'
7import { 9import { getSettings, version } from './cli'
8 version,
9 getSettings
10} from './cli'
11 10
12program 11program
13 .version(version, '-v, --version') 12 .version(version, '-v, --version')
@@ -22,17 +21,19 @@ program
22 .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w') 21 .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w')
23 .command('repl', 'initiate a REPL to access internals') 22 .command('repl', 'initiate a REPL to access internals')
24 .command('plugins [action]', 'manage instance plugins/themes').alias('p') 23 .command('plugins [action]', 'manage instance plugins/themes').alias('p')
24 .command('redundancy [action]', 'manage instance redundancies').alias('r')
25 25
26/* Not Yet Implemented */ 26/* Not Yet Implemented */
27program 27program
28 .command('diagnostic [action]', 28 .command(
29 'like couple therapy, but for your instance', 29 'diagnostic [action]',
30 { noHelp: true } as program.CommandOptions 30 'like couple therapy, but for your instance',
31 ).alias('d') 31 { noHelp: true } as program.CommandOptions
32 ).alias('d')
32 .command('admin', 33 .command('admin',
33 'manage an instance where you have elevated rights', 34 'manage an instance where you have elevated rights',
34 { noHelp: true } as program.CommandOptions 35 { noHelp: true } as program.CommandOptions
35 ).alias('a') 36 ).alias('a')
36 37
37// help on no command 38// help on no command
38if (!process.argv.slice(2).length) { 39if (!process.argv.slice(2).length) {
@@ -81,3 +82,4 @@ getSettings()
81 }) 82 })
82 .parse(process.argv) 83 .parse(process.argv)
83 }) 84 })
85 .catch(err => console.error(err))
diff --git a/server/tools/yarn.lock b/server/tools/yarn.lock
index 28756cbc2..ccd716a51 100644
--- a/server/tools/yarn.lock
+++ b/server/tools/yarn.lock
@@ -347,12 +347,15 @@ chunk-store-stream@^4.0.0:
347 block-stream2 "^2.0.0" 347 block-stream2 "^2.0.0"
348 readable-stream "^3.4.0" 348 readable-stream "^3.4.0"
349 349
350cli-table@^0.3.1: 350cli-table3@^0.5.1:
351 version "0.3.1" 351 version "0.5.1"
352 resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" 352 resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
353 integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM= 353 integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==
354 dependencies: 354 dependencies:
355 colors "1.0.3" 355 object-assign "^4.1.0"
356 string-width "^2.1.1"
357 optionalDependencies:
358 colors "^1.1.2"
356 359
357clivas@^0.2.0: 360clivas@^0.2.0:
358 version "0.2.0" 361 version "0.2.0"
@@ -364,10 +367,10 @@ code-point-at@^1.0.0:
364 resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 367 resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
365 integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= 368 integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
366 369
367colors@1.0.3: 370colors@^1.1.2:
368 version "1.0.3" 371 version "1.4.0"
369 resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" 372 resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
370 integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= 373 integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
371 374
372common-tags@^1.8.0: 375common-tags@^1.8.0:
373 version "1.8.0" 376 version "1.8.0"
@@ -1609,7 +1612,7 @@ string-width@^1.0.1:
1609 is-fullwidth-code-point "^1.0.0" 1612 is-fullwidth-code-point "^1.0.0"
1610 strip-ansi "^3.0.0" 1613 strip-ansi "^3.0.0"
1611 1614
1612"string-width@^1.0.2 || 2": 1615"string-width@^1.0.2 || 2", string-width@^2.1.1:
1613 version "2.1.1" 1616 version "2.1.1"
1614 resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 1617 resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
1615 integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 1618 integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
diff --git a/server/typings/express.ts b/server/typings/express.ts
index 3cc7c7632..f4188bf3d 100644
--- a/server/typings/express.ts
+++ b/server/typings/express.ts
@@ -21,7 +21,7 @@ import {
21} from './models' 21} from './models'
22import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist' 22import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist'
23import { MVideoImportDefault } from '@server/typings/models/video/video-import' 23import { MVideoImportDefault } from '@server/typings/models/video/video-import'
24import { MAccountBlocklist, MStreamingPlaylist, MVideoFile } from '@server/typings/models' 24import { MAccountBlocklist, MActorUrl, MStreamingPlaylist, MVideoFile, MVideoImmutable } from '@server/typings/models'
25import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element' 25import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element'
26import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate' 26import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate'
27import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership' 27import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership'
@@ -35,6 +35,7 @@ declare module 'express' {
35 35
36 locals: { 36 locals: {
37 videoAll?: MVideoFullLight 37 videoAll?: MVideoFullLight
38 onlyImmutableVideo?: MVideoImmutable
38 onlyVideo?: MVideoThumbnail 39 onlyVideo?: MVideoThumbnail
39 onlyVideoWithRights?: MVideoWithRights 40 onlyVideoWithRights?: MVideoWithRights
40 videoId?: MVideoIdThumbnail 41 videoId?: MVideoIdThumbnail
@@ -74,6 +75,7 @@ declare module 'express' {
74 75
75 account?: MAccountDefault 76 account?: MAccountDefault
76 77
78 actorUrl?: MActorUrl
77 actorFull?: MActorFull 79 actorFull?: MActorFull
78 80
79 user?: MUserDefault 81 user?: MUserDefault
diff --git a/server/typings/models/account/account-blocklist.ts b/server/typings/models/account/account-blocklist.ts
index c9cb55332..0d8bf11bd 100644
--- a/server/typings/models/account/account-blocklist.ts
+++ b/server/typings/models/account/account-blocklist.ts
@@ -12,7 +12,8 @@ export type MAccountBlocklist = Omit<AccountBlocklistModel, 'ByAccount' | 'Block
12 12
13export type MAccountBlocklistId = Pick<AccountBlocklistModel, 'id'> 13export type MAccountBlocklistId = Pick<AccountBlocklistModel, 'id'>
14 14
15export type MAccountBlocklistAccounts = MAccountBlocklist & 15export type MAccountBlocklistAccounts =
16 MAccountBlocklist &
16 Use<'ByAccount', MAccountDefault> & 17 Use<'ByAccount', MAccountDefault> &
17 Use<'BlockedAccount', MAccountDefault> 18 Use<'BlockedAccount', MAccountDefault>
18 19
@@ -20,6 +21,7 @@ export type MAccountBlocklistAccounts = MAccountBlocklist &
20 21
21// Format for API or AP object 22// Format for API or AP object
22 23
23export type MAccountBlocklistFormattable = Pick<MAccountBlocklist, 'createdAt'> & 24export type MAccountBlocklistFormattable =
25 Pick<MAccountBlocklist, 'createdAt'> &
24 Use<'ByAccount', MAccountFormattable> & 26 Use<'ByAccount', MAccountFormattable> &
25 Use<'BlockedAccount', MAccountFormattable> 27 Use<'BlockedAccount', MAccountFormattable>
diff --git a/server/typings/models/account/account.ts b/server/typings/models/account/account.ts
index adb1f3689..7b826ee04 100644
--- a/server/typings/models/account/account.ts
+++ b/server/typings/models/account/account.ts
@@ -21,7 +21,8 @@ type Use<K extends keyof AccountModel, M> = PickWith<AccountModel, K, M>
21 21
22// ############################################################################ 22// ############################################################################
23 23
24export type MAccount = Omit<AccountModel, 'Actor' | 'User' | 'Application' | 'VideoChannels' | 'VideoPlaylists' | 24export type MAccount =
25 Omit<AccountModel, 'Actor' | 'User' | 'Application' | 'VideoChannels' | 'VideoPlaylists' |
25 'VideoComments' | 'BlockedAccounts'> 26 'VideoComments' | 'BlockedAccounts'>
26 27
27// ############################################################################ 28// ############################################################################
@@ -34,62 +35,75 @@ export type MAccountUserId = Pick<MAccount, 'userId'>
34export type MAccountUrl = Use<'Actor', MActorUrl> 35export type MAccountUrl = Use<'Actor', MActorUrl>
35export type MAccountAudience = Use<'Actor', MActorAudience> 36export type MAccountAudience = Use<'Actor', MActorAudience>
36 37
37export type MAccountIdActor = MAccountId & 38export type MAccountIdActor =
39 MAccountId &
38 Use<'Actor', MActor> 40 Use<'Actor', MActor>
39 41
40export type MAccountIdActorId = MAccountId & 42export type MAccountIdActorId =
43 MAccountId &
41 Use<'Actor', MActorId> 44 Use<'Actor', MActorId>
42 45
43// ############################################################################ 46// ############################################################################
44 47
45// Default scope 48// Default scope
46export type MAccountDefault = MAccount & 49export type MAccountDefault =
50 MAccount &
47 Use<'Actor', MActorDefault> 51 Use<'Actor', MActorDefault>
48 52
49// Default with default association scopes 53// Default with default association scopes
50export type MAccountDefaultChannelDefault = MAccount & 54export type MAccountDefaultChannelDefault =
55 MAccount &
51 Use<'Actor', MActorDefault> & 56 Use<'Actor', MActorDefault> &
52 Use<'VideoChannels', MChannelDefault[]> 57 Use<'VideoChannels', MChannelDefault[]>
53 58
54// We don't need some actors attributes 59// We don't need some actors attributes
55export type MAccountLight = MAccount & 60export type MAccountLight =
61 MAccount &
56 Use<'Actor', MActorDefaultLight> 62 Use<'Actor', MActorDefaultLight>
57 63
58// ############################################################################ 64// ############################################################################
59 65
60// Full actor 66// Full actor
61export type MAccountActor = MAccount & 67export type MAccountActor =
68 MAccount &
62 Use<'Actor', MActor> 69 Use<'Actor', MActor>
63 70
64// Full actor with server 71// Full actor with server
65export type MAccountServer = MAccount & 72export type MAccountServer =
73 MAccount &
66 Use<'Actor', MActorServer> 74 Use<'Actor', MActorServer>
67 75
68// ############################################################################ 76// ############################################################################
69 77
70// For API 78// For API
71 79
72export type MAccountSummary = FunctionProperties<MAccount> & 80export type MAccountSummary =
81 FunctionProperties<MAccount> &
73 Pick<MAccount, 'id' | 'name'> & 82 Pick<MAccount, 'id' | 'name'> &
74 Use<'Actor', MActorSummary> 83 Use<'Actor', MActorSummary>
75 84
76export type MAccountSummaryBlocks = MAccountSummary & 85export type MAccountSummaryBlocks =
86 MAccountSummary &
77 Use<'BlockedAccounts', MAccountBlocklistId[]> 87 Use<'BlockedAccounts', MAccountBlocklistId[]>
78 88
79export type MAccountAPI = MAccount & 89export type MAccountAPI =
90 MAccount &
80 Use<'Actor', MActorAPI> 91 Use<'Actor', MActorAPI>
81 92
82// ############################################################################ 93// ############################################################################
83 94
84// Format for API or AP object 95// Format for API or AP object
85 96
86export type MAccountSummaryFormattable = FunctionProperties<MAccount> & 97export type MAccountSummaryFormattable =
98 FunctionProperties<MAccount> &
87 Pick<MAccount, 'id' | 'name'> & 99 Pick<MAccount, 'id' | 'name'> &
88 Use<'Actor', MActorSummaryFormattable> 100 Use<'Actor', MActorSummaryFormattable>
89 101
90export type MAccountFormattable = FunctionProperties<MAccount> & 102export type MAccountFormattable =
103 FunctionProperties<MAccount> &
91 Pick<MAccount, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'userId'> & 104 Pick<MAccount, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'userId'> &
92 Use<'Actor', MActorFormattable> 105 Use<'Actor', MActorFormattable>
93 106
94export type MAccountAP = Pick<MAccount, 'name' | 'description'> & 107export type MAccountAP =
108 Pick<MAccount, 'name' | 'description'> &
95 Use<'Actor', MActorAP> 109 Use<'Actor', MActorAP>
diff --git a/server/typings/models/account/actor-follow.ts b/server/typings/models/account/actor-follow.ts
index f44157eba..5d0c03c8d 100644
--- a/server/typings/models/account/actor-follow.ts
+++ b/server/typings/models/account/actor-follow.ts
@@ -20,22 +20,26 @@ export type MActorFollow = Omit<ActorFollowModel, 'ActorFollower' | 'ActorFollow
20 20
21// ############################################################################ 21// ############################################################################
22 22
23export type MActorFollowFollowingHost = MActorFollow & 23export type MActorFollowFollowingHost =
24 MActorFollow &
24 Use<'ActorFollowing', MActorUsername & MActorHost> 25 Use<'ActorFollowing', MActorUsername & MActorHost>
25 26
26// ############################################################################ 27// ############################################################################
27 28
28// With actors or actors default 29// With actors or actors default
29 30
30export type MActorFollowActors = MActorFollow & 31export type MActorFollowActors =
32 MActorFollow &
31 Use<'ActorFollower', MActor> & 33 Use<'ActorFollower', MActor> &
32 Use<'ActorFollowing', MActor> 34 Use<'ActorFollowing', MActor>
33 35
34export type MActorFollowActorsDefault = MActorFollow & 36export type MActorFollowActorsDefault =
37 MActorFollow &
35 Use<'ActorFollower', MActorDefault> & 38 Use<'ActorFollower', MActorDefault> &
36 Use<'ActorFollowing', MActorDefault> 39 Use<'ActorFollowing', MActorDefault>
37 40
38export type MActorFollowFull = MActorFollow & 41export type MActorFollowFull =
42 MActorFollow &
39 Use<'ActorFollower', MActorDefaultAccountChannel> & 43 Use<'ActorFollower', MActorDefaultAccountChannel> &
40 Use<'ActorFollowing', MActorDefaultAccountChannel> 44 Use<'ActorFollowing', MActorDefaultAccountChannel>
41 45
@@ -43,20 +47,24 @@ export type MActorFollowFull = MActorFollow &
43 47
44// For subscriptions 48// For subscriptions
45 49
46type SubscriptionFollowing = MActorDefault & 50type SubscriptionFollowing =
51 MActorDefault &
47 PickWith<ActorModel, 'VideoChannel', MChannelDefault> 52 PickWith<ActorModel, 'VideoChannel', MChannelDefault>
48 53
49export type MActorFollowActorsDefaultSubscription = MActorFollow & 54export type MActorFollowActorsDefaultSubscription =
55 MActorFollow &
50 Use<'ActorFollower', MActorDefault> & 56 Use<'ActorFollower', MActorDefault> &
51 Use<'ActorFollowing', SubscriptionFollowing> 57 Use<'ActorFollowing', SubscriptionFollowing>
52 58
53export type MActorFollowSubscriptions = MActorFollow & 59export type MActorFollowSubscriptions =
60 MActorFollow &
54 Use<'ActorFollowing', MActorChannelAccountActor> 61 Use<'ActorFollowing', MActorChannelAccountActor>
55 62
56// ############################################################################ 63// ############################################################################
57 64
58// Format for API or AP object 65// Format for API or AP object
59 66
60export type MActorFollowFormattable = Pick<MActorFollow, 'id' | 'score' | 'state' | 'createdAt' | 'updatedAt'> & 67export type MActorFollowFormattable =
68 Pick<MActorFollow, 'id' | 'score' | 'state' | 'createdAt' | 'updatedAt'> &
61 Use<'ActorFollower', MActorFormattable> & 69 Use<'ActorFollower', MActorFormattable> &
62 Use<'ActorFollowing', MActorFormattable> 70 Use<'ActorFollowing', MActorFormattable>
diff --git a/server/typings/models/account/actor.ts b/server/typings/models/account/actor.ts
index ee4ece755..1160e84cb 100644
--- a/server/typings/models/account/actor.ts
+++ b/server/typings/models/account/actor.ts
@@ -31,18 +31,23 @@ export type MActorLight = Omit<MActor, 'privateKey' | 'privateKey'>
31export type MActorHost = Use<'Server', MServerHost> 31export type MActorHost = Use<'Server', MServerHost>
32export type MActorRedundancyAllowedOpt = PickWithOpt<ActorModel, 'Server', MServerRedundancyAllowed> 32export type MActorRedundancyAllowedOpt = PickWithOpt<ActorModel, 'Server', MServerRedundancyAllowed>
33 33
34export type MActorDefaultLight = MActorLight & 34export type MActorDefaultLight =
35 MActorLight &
35 Use<'Server', MServerHost> & 36 Use<'Server', MServerHost> &
36 Use<'Avatar', MAvatar> 37 Use<'Avatar', MAvatar>
37 38
38export type MActorAccountId = MActor & 39export type MActorAccountId =
40 MActor &
39 Use<'Account', MAccountId> 41 Use<'Account', MAccountId>
40export type MActorAccountIdActor = MActor & 42export type MActorAccountIdActor =
43 MActor &
41 Use<'Account', MAccountIdActor> 44 Use<'Account', MAccountIdActor>
42 45
43export type MActorChannelId = MActor & 46export type MActorChannelId =
47 MActor &
44 Use<'VideoChannel', MChannelId> 48 Use<'VideoChannel', MChannelId>
45export type MActorChannelIdActor = MActor & 49export type MActorChannelIdActor =
50 MActor &
46 Use<'VideoChannel', MChannelIdActor> 51 Use<'VideoChannel', MChannelIdActor>
47 52
48export type MActorAccountChannelId = MActorAccountId & MActorChannelId 53export type MActorAccountChannelId = MActorAccountId & MActorChannelId
@@ -52,38 +57,45 @@ export type MActorAccountChannelIdActor = MActorAccountIdActor & MActorChannelId
52 57
53// Include raw account/channel/server 58// Include raw account/channel/server
54 59
55export type MActorAccount = MActor & 60export type MActorAccount =
61 MActor &
56 Use<'Account', MAccount> 62 Use<'Account', MAccount>
57 63
58export type MActorChannel = MActor & 64export type MActorChannel =
65 MActor &
59 Use<'VideoChannel', MChannel> 66 Use<'VideoChannel', MChannel>
60 67
61export type MActorDefaultAccountChannel = MActorDefault & MActorAccount & MActorChannel 68export type MActorDefaultAccountChannel = MActorDefault & MActorAccount & MActorChannel
62 69
63export type MActorServer = MActor & 70export type MActorServer =
71 MActor &
64 Use<'Server', MServer> 72 Use<'Server', MServer>
65 73
66// ############################################################################ 74// ############################################################################
67 75
68// Complex actor associations 76// Complex actor associations
69 77
70export type MActorDefault = MActor & 78export type MActorDefault =
79 MActor &
71 Use<'Server', MServer> & 80 Use<'Server', MServer> &
72 Use<'Avatar', MAvatar> 81 Use<'Avatar', MAvatar>
73 82
74// Actor with channel that is associated to an account and its actor 83// Actor with channel that is associated to an account and its actor
75// Actor -> VideoChannel -> Account -> Actor 84// Actor -> VideoChannel -> Account -> Actor
76export type MActorChannelAccountActor = MActor & 85export type MActorChannelAccountActor =
86 MActor &
77 Use<'VideoChannel', MChannelAccountActor> 87 Use<'VideoChannel', MChannelAccountActor>
78 88
79export type MActorFull = MActor & 89export type MActorFull =
90 MActor &
80 Use<'Server', MServer> & 91 Use<'Server', MServer> &
81 Use<'Avatar', MAvatar> & 92 Use<'Avatar', MAvatar> &
82 Use<'Account', MAccount> & 93 Use<'Account', MAccount> &
83 Use<'VideoChannel', MChannelAccountActor> 94 Use<'VideoChannel', MChannelAccountActor>
84 95
85// Same than ActorFull, but the account and the channel have their actor 96// Same than ActorFull, but the account and the channel have their actor
86export type MActorFullActor = MActor & 97export type MActorFullActor =
98 MActor &
87 Use<'Server', MServer> & 99 Use<'Server', MServer> &
88 Use<'Avatar', MAvatar> & 100 Use<'Avatar', MAvatar> &
89 Use<'Account', MAccountDefault> & 101 Use<'Account', MAccountDefault> &
@@ -93,29 +105,35 @@ export type MActorFullActor = MActor &
93 105
94// API 106// API
95 107
96export type MActorSummary = FunctionProperties<MActor> & 108export type MActorSummary =
109 FunctionProperties<MActor> &
97 Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> & 110 Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> &
98 Use<'Server', MServerHost> & 111 Use<'Server', MServerHost> &
99 Use<'Avatar', MAvatar> 112 Use<'Avatar', MAvatar>
100 113
101export type MActorSummaryBlocks = MActorSummary & 114export type MActorSummaryBlocks =
115 MActorSummary &
102 Use<'Server', MServerHostBlocks> 116 Use<'Server', MServerHostBlocks>
103 117
104export type MActorAPI = Omit<MActorDefault, 'publicKey' | 'privateKey' | 'inboxUrl' | 'outboxUrl' | 'sharedInboxUrl' | 118export type MActorAPI =
119 Omit<MActorDefault, 'publicKey' | 'privateKey' | 'inboxUrl' | 'outboxUrl' | 'sharedInboxUrl' |
105 'followersUrl' | 'followingUrl' | 'url' | 'createdAt' | 'updatedAt'> 120 'followersUrl' | 'followingUrl' | 'url' | 'createdAt' | 'updatedAt'>
106 121
107// ############################################################################ 122// ############################################################################
108 123
109// Format for API or AP object 124// Format for API or AP object
110 125
111export type MActorSummaryFormattable = FunctionProperties<MActor> & 126export type MActorSummaryFormattable =
127 FunctionProperties<MActor> &
112 Pick<MActor, 'url' | 'preferredUsername'> & 128 Pick<MActor, 'url' | 'preferredUsername'> &
113 Use<'Server', MServerHost> & 129 Use<'Server', MServerHost> &
114 Use<'Avatar', MAvatarFormattable> 130 Use<'Avatar', MAvatarFormattable>
115 131
116export type MActorFormattable = MActorSummaryFormattable & 132export type MActorFormattable =
133 MActorSummaryFormattable &
117 Pick<MActor, 'id' | 'followingCount' | 'followersCount' | 'createdAt' | 'updatedAt'> & 134 Pick<MActor, 'id' | 'followingCount' | 'followersCount' | 'createdAt' | 'updatedAt'> &
118 Use<'Server', MServerHost & Partial<Pick<MServer, 'redundancyAllowed'>>> 135 Use<'Server', MServerHost & Partial<Pick<MServer, 'redundancyAllowed'>>>
119 136
120export type MActorAP = MActor & 137export type MActorAP =
138 MActor &
121 Use<'Avatar', MAvatar> 139 Use<'Avatar', MAvatar>
diff --git a/server/typings/models/account/avatar.ts b/server/typings/models/account/avatar.ts
index 8af6cc787..21b47180f 100644
--- a/server/typings/models/account/avatar.ts
+++ b/server/typings/models/account/avatar.ts
@@ -7,5 +7,6 @@ export type MAvatar = AvatarModel
7 7
8// Format for API or AP object 8// Format for API or AP object
9 9
10export type MAvatarFormattable = FunctionProperties<MAvatar> & 10export type MAvatarFormattable =
11 FunctionProperties<MAvatar> &
11 Pick<MAvatar, 'filename' | 'createdAt' | 'updatedAt'> 12 Pick<MAvatar, 'filename' | 'createdAt' | 'updatedAt'>
diff --git a/server/typings/models/oauth/oauth-token.ts b/server/typings/models/oauth/oauth-token.ts
index 8ef042d4e..b24a95fd8 100644
--- a/server/typings/models/oauth/oauth-token.ts
+++ b/server/typings/models/oauth/oauth-token.ts
@@ -8,6 +8,7 @@ type Use<K extends keyof OAuthTokenModel, M> = PickWith<OAuthTokenModel, K, M>
8 8
9export type MOAuthToken = Omit<OAuthTokenModel, 'User' | 'OAuthClients'> 9export type MOAuthToken = Omit<OAuthTokenModel, 'User' | 'OAuthClients'>
10 10
11export type MOAuthTokenUser = MOAuthToken & 11export type MOAuthTokenUser =
12 MOAuthToken &
12 Use<'User', MUserAccountUrl> & 13 Use<'User', MUserAccountUrl> &
13 { user?: MUserAccountUrl } 14 { user?: MUserAccountUrl }
diff --git a/server/typings/models/server/plugin.ts b/server/typings/models/server/plugin.ts
index 94674c318..83eb83794 100644
--- a/server/typings/models/server/plugin.ts
+++ b/server/typings/models/server/plugin.ts
@@ -6,5 +6,6 @@ export type MPlugin = PluginModel
6 6
7// Format for API or AP object 7// Format for API or AP object
8 8
9export type MPluginFormattable = Pick<MPlugin, 'name' | 'type' | 'version' | 'latestVersion' | 'enabled' | 'uninstalled' 9export type MPluginFormattable =
10 Pick<MPlugin, 'name' | 'type' | 'version' | 'latestVersion' | 'enabled' | 'uninstalled'
10 | 'peertubeEngine' | 'description' | 'homepage' | 'settings' | 'createdAt' | 'updatedAt'> 11 | 'peertubeEngine' | 'description' | 'homepage' | 'settings' | 'createdAt' | 'updatedAt'>
diff --git a/server/typings/models/server/server-blocklist.ts b/server/typings/models/server/server-blocklist.ts
index c3e6230f2..ff6f49176 100644
--- a/server/typings/models/server/server-blocklist.ts
+++ b/server/typings/models/server/server-blocklist.ts
@@ -11,7 +11,8 @@ export type MServerBlocklist = Omit<ServerBlocklistModel, 'ByAccount' | 'Blocked
11 11
12// ############################################################################ 12// ############################################################################
13 13
14export type MServerBlocklistAccountServer = MServerBlocklist & 14export type MServerBlocklistAccountServer =
15 MServerBlocklist &
15 Use<'ByAccount', MAccountDefault> & 16 Use<'ByAccount', MAccountDefault> &
16 Use<'BlockedServer', MServer> 17 Use<'BlockedServer', MServer>
17 18
@@ -19,6 +20,7 @@ export type MServerBlocklistAccountServer = MServerBlocklist &
19 20
20// Format for API or AP object 21// Format for API or AP object
21 22
22export type MServerBlocklistFormattable = Pick<MServerBlocklist, 'createdAt'> & 23export type MServerBlocklistFormattable =
24 Pick<MServerBlocklist, 'createdAt'> &
23 Use<'ByAccount', MAccountFormattable> & 25 Use<'ByAccount', MAccountFormattable> &
24 Use<'BlockedServer', MServerFormattable> 26 Use<'BlockedServer', MServerFormattable>
diff --git a/server/typings/models/server/server.ts b/server/typings/models/server/server.ts
index 190cc0c28..b35e55aeb 100644
--- a/server/typings/models/server/server.ts
+++ b/server/typings/models/server/server.ts
@@ -13,12 +13,14 @@ export type MServer = Omit<ServerModel, 'Actors' | 'BlockedByAccounts'>
13export type MServerHost = Pick<MServer, 'host'> 13export type MServerHost = Pick<MServer, 'host'>
14export type MServerRedundancyAllowed = Pick<MServer, 'redundancyAllowed'> 14export type MServerRedundancyAllowed = Pick<MServer, 'redundancyAllowed'>
15 15
16export type MServerHostBlocks = MServerHost & 16export type MServerHostBlocks =
17 MServerHost &
17 Use<'BlockedByAccounts', MAccountBlocklistId[]> 18 Use<'BlockedByAccounts', MAccountBlocklistId[]>
18 19
19// ############################################################################ 20// ############################################################################
20 21
21// Format for API or AP object 22// Format for API or AP object
22 23
23export type MServerFormattable = FunctionProperties<MServer> & 24export type MServerFormattable =
25 FunctionProperties<MServer> &
24 Pick<MServer, 'host'> 26 Pick<MServer, 'host'>
diff --git a/server/typings/models/user/user-notification.ts b/server/typings/models/user/user-notification.ts
index 1cdc691b0..2080360e1 100644
--- a/server/typings/models/user/user-notification.ts
+++ b/server/typings/models/user/user-notification.ts
@@ -16,59 +16,73 @@ type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationMo
16 16
17// ############################################################################ 17// ############################################################################
18 18
19export namespace UserNotificationIncludes { 19export module UserNotificationIncludes {
20
20 export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name'> 21 export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name'>
21 export type VideoIncludeChannel = VideoInclude & 22 export type VideoIncludeChannel =
23 VideoInclude &
22 PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor> 24 PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor>
23 25
24 export type ActorInclude = Pick<ActorModel, 'preferredUsername' | 'getHost'> & 26 export type ActorInclude =
27 Pick<ActorModel, 'preferredUsername' | 'getHost'> &
25 PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> & 28 PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> &
26 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> 29 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>>
27 30
28 export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'> 31 export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'>
29 export type VideoChannelIncludeActor = VideoChannelInclude & 32 export type VideoChannelIncludeActor =
33 VideoChannelInclude &
30 PickWith<VideoChannelModel, 'Actor', ActorInclude> 34 PickWith<VideoChannelModel, 'Actor', ActorInclude>
31 35
32 export type AccountInclude = Pick<AccountModel, 'id' | 'name' | 'getDisplayName'> 36 export type AccountInclude = Pick<AccountModel, 'id' | 'name' | 'getDisplayName'>
33 export type AccountIncludeActor = AccountInclude & 37 export type AccountIncludeActor =
38 AccountInclude &
34 PickWith<AccountModel, 'Actor', ActorInclude> 39 PickWith<AccountModel, 'Actor', ActorInclude>
35 40
36 export type VideoCommentInclude = Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> & 41 export type VideoCommentInclude =
42 Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> &
37 PickWith<VideoCommentModel, 'Account', AccountIncludeActor> & 43 PickWith<VideoCommentModel, 'Account', AccountIncludeActor> &
38 PickWith<VideoCommentModel, 'Video', VideoInclude> 44 PickWith<VideoCommentModel, 'Video', VideoInclude>
39 45
40 export type VideoAbuseInclude = Pick<VideoAbuseModel, 'id'> & 46 export type VideoAbuseInclude =
47 Pick<VideoAbuseModel, 'id'> &
41 PickWith<VideoAbuseModel, 'Video', VideoInclude> 48 PickWith<VideoAbuseModel, 'Video', VideoInclude>
42 49
43 export type VideoBlacklistInclude = Pick<VideoBlacklistModel, 'id'> & 50 export type VideoBlacklistInclude =
51 Pick<VideoBlacklistModel, 'id'> &
44 PickWith<VideoAbuseModel, 'Video', VideoInclude> 52 PickWith<VideoAbuseModel, 'Video', VideoInclude>
45 53
46 export type VideoImportInclude = Pick<VideoImportModel, 'id' | 'magnetUri' | 'targetUrl' | 'torrentName'> & 54 export type VideoImportInclude =
55 Pick<VideoImportModel, 'id' | 'magnetUri' | 'targetUrl' | 'torrentName'> &
47 PickWith<VideoImportModel, 'Video', VideoInclude> 56 PickWith<VideoImportModel, 'Video', VideoInclude>
48 57
49 export type ActorFollower = Pick<ActorModel, 'preferredUsername' | 'getHost'> & 58 export type ActorFollower =
59 Pick<ActorModel, 'preferredUsername' | 'getHost'> &
50 PickWith<ActorModel, 'Account', AccountInclude> & 60 PickWith<ActorModel, 'Account', AccountInclude> &
51 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> & 61 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> &
52 PickWithOpt<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> 62 PickWithOpt<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>>
53 63
54 export type ActorFollowing = Pick<ActorModel, 'preferredUsername' | 'type' | 'getHost'> & 64 export type ActorFollowing =
65 Pick<ActorModel, 'preferredUsername' | 'type' | 'getHost'> &
55 PickWith<ActorModel, 'VideoChannel', VideoChannelInclude> & 66 PickWith<ActorModel, 'VideoChannel', VideoChannelInclude> &
56 PickWith<ActorModel, 'Account', AccountInclude> & 67 PickWith<ActorModel, 'Account', AccountInclude> &
57 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> 68 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>>
58 69
59 export type ActorFollowInclude = Pick<ActorFollowModel, 'id' | 'state'> & 70 export type ActorFollowInclude =
71 Pick<ActorFollowModel, 'id' | 'state'> &
60 PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> & 72 PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> &
61 PickWith<ActorFollowModel, 'ActorFollowing', ActorFollowing> 73 PickWith<ActorFollowModel, 'ActorFollowing', ActorFollowing>
62} 74}
63 75
64// ############################################################################ 76// ############################################################################
65 77
66export type MUserNotification = Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'VideoAbuse' | 'VideoBlacklist' | 78export type MUserNotification =
79 Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'VideoAbuse' | 'VideoBlacklist' |
67 'VideoImport' | 'Account' | 'ActorFollow'> 80 'VideoImport' | 'Account' | 'ActorFollow'>
68 81
69// ############################################################################ 82// ############################################################################
70 83
71export type UserNotificationModelForApi = MUserNotification & 84export type UserNotificationModelForApi =
85 MUserNotification &
72 Use<'Video', UserNotificationIncludes.VideoIncludeChannel> & 86 Use<'Video', UserNotificationIncludes.VideoIncludeChannel> &
73 Use<'Comment', UserNotificationIncludes.VideoCommentInclude> & 87 Use<'Comment', UserNotificationIncludes.VideoCommentInclude> &
74 Use<'VideoAbuse', UserNotificationIncludes.VideoAbuseInclude> & 88 Use<'VideoAbuse', UserNotificationIncludes.VideoAbuseInclude> &
diff --git a/server/typings/models/user/user.ts b/server/typings/models/user/user.ts
index 6ac19c20b..31cf075ef 100644
--- a/server/typings/models/user/user.ts
+++ b/server/typings/models/user/user.ts
@@ -29,36 +29,44 @@ export type MUserId = Pick<UserModel, 'id'>
29 29
30// With account 30// With account
31 31
32export type MUserAccountId = MUser & 32export type MUserAccountId =
33 MUser &
33 Use<'Account', MAccountId> 34 Use<'Account', MAccountId>
34 35
35export type MUserAccountUrl = MUser & 36export type MUserAccountUrl =
37 MUser &
36 Use<'Account', MAccountUrl & MAccountIdActorId> 38 Use<'Account', MAccountUrl & MAccountIdActorId>
37 39
38export type MUserAccount = MUser & 40export type MUserAccount =
41 MUser &
39 Use<'Account', MAccount> 42 Use<'Account', MAccount>
40 43
41export type MUserAccountDefault = MUser & 44export type MUserAccountDefault =
45 MUser &
42 Use<'Account', MAccountDefault> 46 Use<'Account', MAccountDefault>
43 47
44// With channel 48// With channel
45 49
46export type MUserNotifSettingChannelDefault = MUser & 50export type MUserNotifSettingChannelDefault =
51 MUser &
47 Use<'NotificationSetting', MNotificationSetting> & 52 Use<'NotificationSetting', MNotificationSetting> &
48 Use<'Account', MAccountDefaultChannelDefault> 53 Use<'Account', MAccountDefaultChannelDefault>
49 54
50// With notification settings 55// With notification settings
51 56
52export type MUserWithNotificationSetting = MUser & 57export type MUserWithNotificationSetting =
58 MUser &
53 Use<'NotificationSetting', MNotificationSetting> 59 Use<'NotificationSetting', MNotificationSetting>
54 60
55export type MUserNotifSettingAccount = MUser & 61export type MUserNotifSettingAccount =
62 MUser &
56 Use<'NotificationSetting', MNotificationSetting> & 63 Use<'NotificationSetting', MNotificationSetting> &
57 Use<'Account', MAccount> 64 Use<'Account', MAccount>
58 65
59// Default scope 66// Default scope
60 67
61export type MUserDefault = MUser & 68export type MUserDefault =
69 MUser &
62 Use<'NotificationSetting', MNotificationSetting> & 70 Use<'NotificationSetting', MNotificationSetting> &
63 Use<'Account', MAccountDefault> 71 Use<'Account', MAccountDefault>
64 72
@@ -67,12 +75,15 @@ export type MUserDefault = MUser &
67// Format for API or AP object 75// Format for API or AP object
68 76
69type MAccountWithChannels = MAccountFormattable & PickWithOpt<AccountModel, 'VideoChannels', MChannelFormattable[]> 77type MAccountWithChannels = MAccountFormattable & PickWithOpt<AccountModel, 'VideoChannels', MChannelFormattable[]>
70type MAccountWithChannelsAndSpecialPlaylists = MAccountWithChannels & 78type MAccountWithChannelsAndSpecialPlaylists =
79 MAccountWithChannels &
71 PickWithOpt<AccountModel, 'VideoPlaylists', MVideoPlaylist[]> 80 PickWithOpt<AccountModel, 'VideoPlaylists', MVideoPlaylist[]>
72 81
73export type MUserFormattable = MUserQuotaUsed & 82export type MUserFormattable =
83 MUserQuotaUsed &
74 Use<'Account', MAccountWithChannels> & 84 Use<'Account', MAccountWithChannels> &
75 PickWithOpt<UserModel, 'NotificationSetting', MNotificationSettingFormattable> 85 PickWithOpt<UserModel, 'NotificationSetting', MNotificationSettingFormattable>
76 86
77export type MMyUserFormattable = MUserFormattable & 87export type MMyUserFormattable =
88 MUserFormattable &
78 Use<'Account', MAccountWithChannelsAndSpecialPlaylists> 89 Use<'Account', MAccountWithChannelsAndSpecialPlaylists>
diff --git a/server/typings/models/video/schedule-video-update.ts b/server/typings/models/video/schedule-video-update.ts
index e6f478cdf..95a53d139 100644
--- a/server/typings/models/video/schedule-video-update.ts
+++ b/server/typings/models/video/schedule-video-update.ts
@@ -10,7 +10,8 @@ export type MScheduleVideoUpdate = Omit<ScheduleVideoUpdateModel, 'Video'>
10 10
11// ############################################################################ 11// ############################################################################
12 12
13export type MScheduleVideoUpdateVideoAll = MScheduleVideoUpdate & 13export type MScheduleVideoUpdateVideoAll =
14 MScheduleVideoUpdate &
14 Use<'Video', MVideoAPWithoutCaption & MVideoWithBlacklistLight> 15 Use<'Video', MVideoAPWithoutCaption & MVideoWithBlacklistLight>
15 16
16// Format for API or AP object 17// Format for API or AP object
diff --git a/server/typings/models/video/video-abuse.ts b/server/typings/models/video/video-abuse.ts
index e38c3f586..955ec4780 100644
--- a/server/typings/models/video/video-abuse.ts
+++ b/server/typings/models/video/video-abuse.ts
@@ -13,11 +13,13 @@ export type MVideoAbuse = Omit<VideoAbuseModel, 'Account' | 'Video' | 'toActivit
13 13
14export type MVideoAbuseId = Pick<VideoAbuseModel, 'id'> 14export type MVideoAbuseId = Pick<VideoAbuseModel, 'id'>
15 15
16export type MVideoAbuseVideo = MVideoAbuse & 16export type MVideoAbuseVideo =
17 MVideoAbuse &
17 Pick<VideoAbuseModel, 'toActivityPubObject'> & 18 Pick<VideoAbuseModel, 'toActivityPubObject'> &
18 Use<'Video', MVideo> 19 Use<'Video', MVideo>
19 20
20export type MVideoAbuseAccountVideo = MVideoAbuse & 21export type MVideoAbuseAccountVideo =
22 MVideoAbuse &
21 Pick<VideoAbuseModel, 'toActivityPubObject'> & 23 Pick<VideoAbuseModel, 'toActivityPubObject'> &
22 Use<'Video', MVideo> & 24 Use<'Video', MVideo> &
23 Use<'Account', MAccountDefault> 25 Use<'Account', MAccountDefault>
@@ -26,6 +28,7 @@ export type MVideoAbuseAccountVideo = MVideoAbuse &
26 28
27// Format for API or AP object 29// Format for API or AP object
28 30
29export type MVideoAbuseFormattable = MVideoAbuse & 31export type MVideoAbuseFormattable =
32 MVideoAbuse &
30 Use<'Account', MAccountFormattable> & 33 Use<'Account', MAccountFormattable> &
31 Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name'>> 34 Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name'>>
diff --git a/server/typings/models/video/video-blacklist.ts b/server/typings/models/video/video-blacklist.ts
index 7122a9dc0..ddb4db832 100644
--- a/server/typings/models/video/video-blacklist.ts
+++ b/server/typings/models/video/video-blacklist.ts
@@ -13,15 +13,18 @@ export type MVideoBlacklistUnfederated = Pick<MVideoBlacklist, 'unfederated'>
13 13
14// ############################################################################ 14// ############################################################################
15 15
16export type MVideoBlacklistLightVideo = MVideoBlacklistLight & 16export type MVideoBlacklistLightVideo =
17 MVideoBlacklistLight &
17 Use<'Video', MVideo> 18 Use<'Video', MVideo>
18 19
19export type MVideoBlacklistVideo = MVideoBlacklist & 20export type MVideoBlacklistVideo =
21 MVideoBlacklist &
20 Use<'Video', MVideo> 22 Use<'Video', MVideo>
21 23
22// ############################################################################ 24// ############################################################################
23 25
24// Format for API or AP object 26// Format for API or AP object
25 27
26export type MVideoBlacklistFormattable = MVideoBlacklist & 28export type MVideoBlacklistFormattable =
29 MVideoBlacklist &
27 Use<'Video', MVideoFormattable> 30 Use<'Video', MVideoFormattable>
diff --git a/server/typings/models/video/video-caption.ts b/server/typings/models/video/video-caption.ts
index ffa56f544..e7aff6956 100644
--- a/server/typings/models/video/video-caption.ts
+++ b/server/typings/models/video/video-caption.ts
@@ -11,14 +11,17 @@ export type MVideoCaption = Omit<VideoCaptionModel, 'Video'>
11// ############################################################################ 11// ############################################################################
12 12
13export type MVideoCaptionLanguage = Pick<MVideoCaption, 'language'> 13export type MVideoCaptionLanguage = Pick<MVideoCaption, 'language'>
14export type MVideoCaptionLanguageUrl = Pick<MVideoCaption, 'language' | 'fileUrl' | 'getFileUrl'>
14 15
15export type MVideoCaptionVideo = MVideoCaption & 16export type MVideoCaptionVideo =
17 MVideoCaption &
16 Use<'Video', Pick<MVideo, 'id' | 'remote' | 'uuid'>> 18 Use<'Video', Pick<MVideo, 'id' | 'remote' | 'uuid'>>
17 19
18// ############################################################################ 20// ############################################################################
19 21
20// Format for API or AP object 22// Format for API or AP object
21 23
22export type MVideoCaptionFormattable = FunctionProperties<MVideoCaption> & 24export type MVideoCaptionFormattable =
25 FunctionProperties<MVideoCaption> &
23 Pick<MVideoCaption, 'language'> & 26 Pick<MVideoCaption, 'language'> &
24 Use<'Video', MVideoUUID> 27 Use<'Video', MVideoUUID>
diff --git a/server/typings/models/video/video-change-ownership.ts b/server/typings/models/video/video-change-ownership.ts
index e5b5bbc1d..971dc3db5 100644
--- a/server/typings/models/video/video-change-ownership.ts
+++ b/server/typings/models/video/video-change-ownership.ts
@@ -9,7 +9,8 @@ type Use<K extends keyof VideoChangeOwnershipModel, M> = PickWith<VideoChangeOwn
9 9
10export type MVideoChangeOwnership = Omit<VideoChangeOwnershipModel, 'Initiator' | 'NextOwner' | 'Video'> 10export type MVideoChangeOwnership = Omit<VideoChangeOwnershipModel, 'Initiator' | 'NextOwner' | 'Video'>
11 11
12export type MVideoChangeOwnershipFull = MVideoChangeOwnership & 12export type MVideoChangeOwnershipFull =
13 MVideoChangeOwnership &
13 Use<'Initiator', MAccountDefault> & 14 Use<'Initiator', MAccountDefault> &
14 Use<'NextOwner', MAccountDefault> & 15 Use<'NextOwner', MAccountDefault> &
15 Use<'Video', MVideoWithAllFiles> 16 Use<'Video', MVideoWithAllFiles>
@@ -18,7 +19,8 @@ export type MVideoChangeOwnershipFull = MVideoChangeOwnership &
18 19
19// Format for API or AP object 20// Format for API or AP object
20 21
21export type MVideoChangeOwnershipFormattable = Pick<MVideoChangeOwnership, 'id' | 'status' | 'createdAt'> & 22export type MVideoChangeOwnershipFormattable =
23 Pick<MVideoChangeOwnership, 'id' | 'status' | 'createdAt'> &
22 Use<'Initiator', MAccountFormattable> & 24 Use<'Initiator', MAccountFormattable> &
23 Use<'NextOwner', MAccountFormattable> & 25 Use<'NextOwner', MAccountFormattable> &
24 Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'url' | 'name'>> 26 Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'url' | 'name'>>
diff --git a/server/typings/models/video/video-channels.ts b/server/typings/models/video/video-channels.ts
index 292d0ac95..50f7c2d8a 100644
--- a/server/typings/models/video/video-channels.ts
+++ b/server/typings/models/video/video-channels.ts
@@ -35,32 +35,39 @@ export type MChannelId = Pick<MChannel, 'id'>
35 35
36// ############################################################################ 36// ############################################################################
37 37
38export type MChannelIdActor = MChannelId & 38export type MChannelIdActor =
39 MChannelId &
39 Use<'Actor', MActorAccountChannelId> 40 Use<'Actor', MActorAccountChannelId>
40 41
41export type MChannelUserId = Pick<MChannel, 'accountId'> & 42export type MChannelUserId =
43 Pick<MChannel, 'accountId'> &
42 Use<'Account', MAccountUserId> 44 Use<'Account', MAccountUserId>
43 45
44export type MChannelActor = MChannel & 46export type MChannelActor =
47 MChannel &
45 Use<'Actor', MActor> 48 Use<'Actor', MActor>
46 49
47export type MChannelUrl = Use<'Actor', MActorUrl> 50export type MChannelUrl = Use<'Actor', MActorUrl>
48 51
49// Default scope 52// Default scope
50export type MChannelDefault = MChannel & 53export type MChannelDefault =
54 MChannel &
51 Use<'Actor', MActorDefault> 55 Use<'Actor', MActorDefault>
52 56
53// ############################################################################ 57// ############################################################################
54 58
55// Not all association attributes 59// Not all association attributes
56 60
57export type MChannelLight = MChannel & 61export type MChannelLight =
62 MChannel &
58 Use<'Actor', MActorDefaultLight> 63 Use<'Actor', MActorDefaultLight>
59 64
60export type MChannelActorLight = MChannel & 65export type MChannelActorLight =
66 MChannel &
61 Use<'Actor', MActorLight> 67 Use<'Actor', MActorLight>
62 68
63export type MChannelAccountLight = MChannel & 69export type MChannelAccountLight =
70 MChannel &
64 Use<'Actor', MActorDefaultLight> & 71 Use<'Actor', MActorDefaultLight> &
65 Use<'Account', MAccountLight> 72 Use<'Account', MAccountLight>
66 73
@@ -68,24 +75,29 @@ export type MChannelAccountLight = MChannel &
68 75
69// Account associations 76// Account associations
70 77
71export type MChannelAccountActor = MChannel & 78export type MChannelAccountActor =
79 MChannel &
72 Use<'Account', MAccountActor> 80 Use<'Account', MAccountActor>
73 81
74export type MChannelAccountDefault = MChannel & 82export type MChannelAccountDefault =
83 MChannel &
75 Use<'Actor', MActorDefault> & 84 Use<'Actor', MActorDefault> &
76 Use<'Account', MAccountDefault> 85 Use<'Account', MAccountDefault>
77 86
78export type MChannelActorAccountActor = MChannel & 87export type MChannelActorAccountActor =
88 MChannel &
79 Use<'Account', MAccountActor> & 89 Use<'Account', MAccountActor> &
80 Use<'Actor', MActor> 90 Use<'Actor', MActor>
81 91
82// ############################################################################ 92// ############################################################################
83 93
84// Videos associations 94// Videos associations
85export type MChannelVideos = MChannel & 95export type MChannelVideos =
96 MChannel &
86 Use<'Videos', MVideo[]> 97 Use<'Videos', MVideo[]>
87 98
88export type MChannelActorAccountDefaultVideos = MChannel & 99export type MChannelActorAccountDefaultVideos =
100 MChannel &
89 Use<'Actor', MActorDefault> & 101 Use<'Actor', MActorDefault> &
90 Use<'Account', MAccountDefault> & 102 Use<'Account', MAccountDefault> &
91 Use<'Videos', MVideo[]> 103 Use<'Videos', MVideo[]>
@@ -94,14 +106,17 @@ export type MChannelActorAccountDefaultVideos = MChannel &
94 106
95// For API 107// For API
96 108
97export type MChannelSummary = FunctionProperties<MChannel> & 109export type MChannelSummary =
110 FunctionProperties<MChannel> &
98 Pick<MChannel, 'id' | 'name' | 'description' | 'actorId'> & 111 Pick<MChannel, 'id' | 'name' | 'description' | 'actorId'> &
99 Use<'Actor', MActorSummary> 112 Use<'Actor', MActorSummary>
100 113
101export type MChannelSummaryAccount = MChannelSummary & 114export type MChannelSummaryAccount =
115 MChannelSummary &
102 Use<'Account', MAccountSummaryBlocks> 116 Use<'Account', MAccountSummaryBlocks>
103 117
104export type MChannelAPI = MChannel & 118export type MChannelAPI =
119 MChannel &
105 Use<'Actor', MActorAPI> & 120 Use<'Actor', MActorAPI> &
106 Use<'Account', MAccountAPI> 121 Use<'Account', MAccountAPI>
107 122
@@ -109,18 +124,22 @@ export type MChannelAPI = MChannel &
109 124
110// Format for API or AP object 125// Format for API or AP object
111 126
112export type MChannelSummaryFormattable = FunctionProperties<MChannel> & 127export type MChannelSummaryFormattable =
128 FunctionProperties<MChannel> &
113 Pick<MChannel, 'id' | 'name'> & 129 Pick<MChannel, 'id' | 'name'> &
114 Use<'Actor', MActorSummaryFormattable> 130 Use<'Actor', MActorSummaryFormattable>
115 131
116export type MChannelAccountSummaryFormattable = MChannelSummaryFormattable & 132export type MChannelAccountSummaryFormattable =
133 MChannelSummaryFormattable &
117 Use<'Account', MAccountSummaryFormattable> 134 Use<'Account', MAccountSummaryFormattable>
118 135
119export type MChannelFormattable = FunctionProperties<MChannel> & 136export type MChannelFormattable =
137 FunctionProperties<MChannel> &
120 Pick<MChannel, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'support'> & 138 Pick<MChannel, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'support'> &
121 Use<'Actor', MActorFormattable> & 139 Use<'Actor', MActorFormattable> &
122 PickWithOpt<VideoChannelModel, 'Account', MAccountFormattable> 140 PickWithOpt<VideoChannelModel, 'Account', MAccountFormattable>
123 141
124export type MChannelAP = Pick<MChannel, 'name' | 'description' | 'support'> & 142export type MChannelAP =
143 Pick<MChannel, 'name' | 'description' | 'support'> &
125 Use<'Actor', MActorAP> & 144 Use<'Actor', MActorAP> &
126 Use<'Account', MAccountUrl> 145 Use<'Account', MAccountUrl>
diff --git a/server/typings/models/video/video-comment.ts b/server/typings/models/video/video-comment.ts
index d693f9186..d6e0b66f5 100644
--- a/server/typings/models/video/video-comment.ts
+++ b/server/typings/models/video/video-comment.ts
@@ -14,30 +14,37 @@ export type MCommentUrl = Pick<MComment, 'url'>
14 14
15// ############################################################################ 15// ############################################################################
16 16
17export type MCommentOwner = MComment & 17export type MCommentOwner =
18 MComment &
18 Use<'Account', MAccountDefault> 19 Use<'Account', MAccountDefault>
19 20
20export type MCommentVideo = MComment & 21export type MCommentVideo =
22 MComment &
21 Use<'Video', MVideoAccountLight> 23 Use<'Video', MVideoAccountLight>
22 24
23export type MCommentReply = MComment & 25export type MCommentReply =
26 MComment &
24 Use<'InReplyToVideoComment', MComment> 27 Use<'InReplyToVideoComment', MComment>
25 28
26export type MCommentOwnerVideo = MComment & 29export type MCommentOwnerVideo =
30 MComment &
27 Use<'Account', MAccountDefault> & 31 Use<'Account', MAccountDefault> &
28 Use<'Video', MVideoAccountLight> 32 Use<'Video', MVideoAccountLight>
29 33
30export type MCommentOwnerVideoReply = MComment & 34export type MCommentOwnerVideoReply =
35 MComment &
31 Use<'Account', MAccountDefault> & 36 Use<'Account', MAccountDefault> &
32 Use<'Video', MVideoAccountLight> & 37 Use<'Video', MVideoAccountLight> &
33 Use<'InReplyToVideoComment', MComment> 38 Use<'InReplyToVideoComment', MComment>
34 39
35export type MCommentOwnerReplyVideoLight = MComment & 40export type MCommentOwnerReplyVideoLight =
41 MComment &
36 Use<'Account', MAccountDefault> & 42 Use<'Account', MAccountDefault> &
37 Use<'InReplyToVideoComment', MComment> & 43 Use<'InReplyToVideoComment', MComment> &
38 Use<'Video', MVideoIdUrl> 44 Use<'Video', MVideoIdUrl>
39 45
40export type MCommentOwnerVideoFeed = MCommentOwner & 46export type MCommentOwnerVideoFeed =
47 MCommentOwner &
41 Use<'Video', MVideoFeed> 48 Use<'Video', MVideoFeed>
42 49
43// ############################################################################ 50// ############################################################################
@@ -48,10 +55,12 @@ export type MCommentAPI = MComment & { totalReplies: number }
48 55
49// Format for API or AP object 56// Format for API or AP object
50 57
51export type MCommentFormattable = MCommentTotalReplies & 58export type MCommentFormattable =
59 MCommentTotalReplies &
52 Use<'Account', MAccountFormattable> 60 Use<'Account', MAccountFormattable>
53 61
54export type MCommentAP = MComment & 62export type MCommentAP =
63 MComment &
55 Use<'Account', MAccountUrl> & 64 Use<'Account', MAccountUrl> &
56 PickWithOpt<VideoCommentModel, 'Video', MVideoUrl> & 65 PickWithOpt<VideoCommentModel, 'Video', MVideoUrl> &
57 PickWithOpt<VideoCommentModel, 'InReplyToVideoComment', MCommentUrl> 66 PickWithOpt<VideoCommentModel, 'InReplyToVideoComment', MCommentUrl>
diff --git a/server/typings/models/video/video-file.ts b/server/typings/models/video/video-file.ts
index 352fe3d32..3fcaca78f 100644
--- a/server/typings/models/video/video-file.ts
+++ b/server/typings/models/video/video-file.ts
@@ -1,7 +1,7 @@
1import { VideoFileModel } from '../../../models/video/video-file' 1import { VideoFileModel } from '../../../models/video/video-file'
2import { PickWith, PickWithOpt } from '../../utils' 2import { PickWith, PickWithOpt } from '../../utils'
3import { MVideo, MVideoUUID } from './video' 3import { MVideo, MVideoUUID } from './video'
4import { MVideoRedundancyFileUrl } from './video-redundancy' 4import { MVideoRedundancy, MVideoRedundancyFileUrl } from './video-redundancy'
5import { MStreamingPlaylistVideo, MStreamingPlaylist } from './video-streaming-playlist' 5import { MStreamingPlaylistVideo, MStreamingPlaylist } from './video-streaming-playlist'
6 6
7type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M> 7type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M>
@@ -10,19 +10,28 @@ type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M>
10 10
11export type MVideoFile = Omit<VideoFileModel, 'Video' | 'RedundancyVideos' | 'VideoStreamingPlaylist'> 11export type MVideoFile = Omit<VideoFileModel, 'Video' | 'RedundancyVideos' | 'VideoStreamingPlaylist'>
12 12
13export type MVideoFileVideo = MVideoFile & 13export type MVideoFileVideo =
14 MVideoFile &
14 Use<'Video', MVideo> 15 Use<'Video', MVideo>
15 16
16export type MVideoFileStreamingPlaylist = MVideoFile & 17export type MVideoFileStreamingPlaylist =
18 MVideoFile &
17 Use<'VideoStreamingPlaylist', MStreamingPlaylist> 19 Use<'VideoStreamingPlaylist', MStreamingPlaylist>
18 20
19export type MVideoFileStreamingPlaylistVideo = MVideoFile & 21export type MVideoFileStreamingPlaylistVideo =
22 MVideoFile &
20 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> 23 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo>
21 24
22export type MVideoFileVideoUUID = MVideoFile & 25export type MVideoFileVideoUUID =
26 MVideoFile &
23 Use<'Video', MVideoUUID> 27 Use<'Video', MVideoUUID>
24 28
25export type MVideoFileRedundanciesOpt = MVideoFile & 29export type MVideoFileRedundanciesAll =
30 MVideoFile &
31 PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancy[]>
32
33export type MVideoFileRedundanciesOpt =
34 MVideoFile &
26 PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]> 35 PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]>
27 36
28export function isStreamingPlaylistFile (file: any): file is MVideoFileStreamingPlaylist { 37export function isStreamingPlaylistFile (file: any): file is MVideoFileStreamingPlaylist {
diff --git a/server/typings/models/video/video-import.ts b/server/typings/models/video/video-import.ts
index e119f17f9..4e5c2e4f0 100644
--- a/server/typings/models/video/video-import.ts
+++ b/server/typings/models/video/video-import.ts
@@ -9,18 +9,21 @@ type Use<K extends keyof VideoImportModel, M> = PickWith<VideoImportModel, K, M>
9 9
10export type MVideoImport = Omit<VideoImportModel, 'User' | 'Video'> 10export type MVideoImport = Omit<VideoImportModel, 'User' | 'Video'>
11 11
12export type MVideoImportVideo = MVideoImport & 12export type MVideoImportVideo =
13 MVideoImport &
13 Use<'Video', MVideo> 14 Use<'Video', MVideo>
14 15
15// ############################################################################ 16// ############################################################################
16 17
17type VideoAssociation = MVideoTag & MVideoAccountLight & MVideoThumbnail 18type VideoAssociation = MVideoTag & MVideoAccountLight & MVideoThumbnail
18 19
19export type MVideoImportDefault = MVideoImport & 20export type MVideoImportDefault =
21 MVideoImport &
20 Use<'User', MUser> & 22 Use<'User', MUser> &
21 Use<'Video', VideoAssociation> 23 Use<'Video', VideoAssociation>
22 24
23export type MVideoImportDefaultFiles = MVideoImport & 25export type MVideoImportDefaultFiles =
26 MVideoImport &
24 Use<'User', MUser> & 27 Use<'User', MUser> &
25 Use<'Video', VideoAssociation & MVideoWithFile> 28 Use<'Video', VideoAssociation & MVideoWithFile>
26 29
@@ -28,5 +31,6 @@ export type MVideoImportDefaultFiles = MVideoImport &
28 31
29// Format for API or AP object 32// Format for API or AP object
30 33
31export type MVideoImportFormattable = MVideoImport & 34export type MVideoImportFormattable =
35 MVideoImport &
32 PickWithOpt<VideoImportModel, 'Video', MVideoFormattable & MVideoTag> 36 PickWithOpt<VideoImportModel, 'Video', MVideoFormattable & MVideoTag>
diff --git a/server/typings/models/video/video-playlist-element.ts b/server/typings/models/video/video-playlist-element.ts
index 1aeff78d8..f33c76594 100644
--- a/server/typings/models/video/video-playlist-element.ts
+++ b/server/typings/models/video/video-playlist-element.ts
@@ -17,10 +17,12 @@ export type MVideoPlaylistElementLight = Pick<MVideoPlaylistElement, 'id' | 'vid
17 17
18// ############################################################################ 18// ############################################################################
19 19
20export type MVideoPlaylistVideoThumbnail = MVideoPlaylistElement & 20export type MVideoPlaylistVideoThumbnail =
21 MVideoPlaylistElement &
21 Use<'Video', MVideoThumbnail> 22 Use<'Video', MVideoThumbnail>
22 23
23export type MVideoPlaylistElementVideoUrlPlaylistPrivacy = MVideoPlaylistElement & 24export type MVideoPlaylistElementVideoUrlPlaylistPrivacy =
25 MVideoPlaylistElement &
24 Use<'Video', MVideoUrl> & 26 Use<'Video', MVideoUrl> &
25 Use<'VideoPlaylist', MVideoPlaylistPrivacy> 27 Use<'VideoPlaylist', MVideoPlaylistPrivacy>
26 28
@@ -28,8 +30,10 @@ export type MVideoPlaylistElementVideoUrlPlaylistPrivacy = MVideoPlaylistElement
28 30
29// Format for API or AP object 31// Format for API or AP object
30 32
31export type MVideoPlaylistElementFormattable = MVideoPlaylistElement & 33export type MVideoPlaylistElementFormattable =
34 MVideoPlaylistElement &
32 Use<'Video', MVideoFormattable> 35 Use<'Video', MVideoFormattable>
33 36
34export type MVideoPlaylistElementAP = MVideoPlaylistElement & 37export type MVideoPlaylistElementAP =
38 MVideoPlaylistElement &
35 Use<'Video', MVideoUrl> 39 Use<'Video', MVideoUrl>
diff --git a/server/typings/models/video/video-playlist.ts b/server/typings/models/video/video-playlist.ts
index a40c7aca9..49c27f4a7 100644
--- a/server/typings/models/video/video-playlist.ts
+++ b/server/typings/models/video/video-playlist.ts
@@ -22,30 +22,36 @@ export type MVideoPlaylistVideosLength = MVideoPlaylist & { videosLength?: numbe
22 22
23// With elements 23// With elements
24 24
25export type MVideoPlaylistWithElements = MVideoPlaylist & 25export type MVideoPlaylistWithElements =
26 MVideoPlaylist &
26 Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]> 27 Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]>
27 28
28export type MVideoPlaylistIdWithElements = MVideoPlaylistId & 29export type MVideoPlaylistIdWithElements =
30 MVideoPlaylistId &
29 Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]> 31 Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]>
30 32
31// ############################################################################ 33// ############################################################################
32 34
33// With account 35// With account
34 36
35export type MVideoPlaylistOwner = MVideoPlaylist & 37export type MVideoPlaylistOwner =
38 MVideoPlaylist &
36 Use<'OwnerAccount', MAccount> 39 Use<'OwnerAccount', MAccount>
37 40
38export type MVideoPlaylistOwnerDefault = MVideoPlaylist & 41export type MVideoPlaylistOwnerDefault =
42 MVideoPlaylist &
39 Use<'OwnerAccount', MAccountDefault> 43 Use<'OwnerAccount', MAccountDefault>
40 44
41// ############################################################################ 45// ############################################################################
42 46
43// With thumbnail 47// With thumbnail
44 48
45export type MVideoPlaylistThumbnail = MVideoPlaylist & 49export type MVideoPlaylistThumbnail =
50 MVideoPlaylist &
46 Use<'Thumbnail', MThumbnail> 51 Use<'Thumbnail', MThumbnail>
47 52
48export type MVideoPlaylistAccountThumbnail = MVideoPlaylist & 53export type MVideoPlaylistAccountThumbnail =
54 MVideoPlaylist &
49 Use<'OwnerAccount', MAccountDefault> & 55 Use<'OwnerAccount', MAccountDefault> &
50 Use<'Thumbnail', MThumbnail> 56 Use<'Thumbnail', MThumbnail>
51 57
@@ -53,7 +59,8 @@ export type MVideoPlaylistAccountThumbnail = MVideoPlaylist &
53 59
54// With channel 60// With channel
55 61
56export type MVideoPlaylistAccountChannelDefault = MVideoPlaylist & 62export type MVideoPlaylistAccountChannelDefault =
63 MVideoPlaylist &
57 Use<'OwnerAccount', MAccountDefault> & 64 Use<'OwnerAccount', MAccountDefault> &
58 Use<'VideoChannel', MChannelDefault> 65 Use<'VideoChannel', MChannelDefault>
59 66
@@ -61,7 +68,8 @@ export type MVideoPlaylistAccountChannelDefault = MVideoPlaylist &
61 68
62// With all associations 69// With all associations
63 70
64export type MVideoPlaylistFull = MVideoPlaylist & 71export type MVideoPlaylistFull =
72 MVideoPlaylist &
65 Use<'OwnerAccount', MAccountDefault> & 73 Use<'OwnerAccount', MAccountDefault> &
66 Use<'VideoChannel', MChannelDefault> & 74 Use<'VideoChannel', MChannelDefault> &
67 Use<'Thumbnail', MThumbnail> 75 Use<'Thumbnail', MThumbnail>
@@ -70,11 +78,13 @@ export type MVideoPlaylistFull = MVideoPlaylist &
70 78
71// For API 79// For API
72 80
73export type MVideoPlaylistAccountChannelSummary = MVideoPlaylist & 81export type MVideoPlaylistAccountChannelSummary =
82 MVideoPlaylist &
74 Use<'OwnerAccount', MAccountSummary> & 83 Use<'OwnerAccount', MAccountSummary> &
75 Use<'VideoChannel', MChannelSummary> 84 Use<'VideoChannel', MChannelSummary>
76 85
77export type MVideoPlaylistFullSummary = MVideoPlaylist & 86export type MVideoPlaylistFullSummary =
87 MVideoPlaylist &
78 Use<'Thumbnail', MThumbnail> & 88 Use<'Thumbnail', MThumbnail> &
79 Use<'OwnerAccount', MAccountSummary> & 89 Use<'OwnerAccount', MAccountSummary> &
80 Use<'VideoChannel', MChannelSummary> 90 Use<'VideoChannel', MChannelSummary>
@@ -83,10 +93,12 @@ export type MVideoPlaylistFullSummary = MVideoPlaylist &
83 93
84// Format for API or AP object 94// Format for API or AP object
85 95
86export type MVideoPlaylistFormattable = MVideoPlaylistVideosLength & 96export type MVideoPlaylistFormattable =
97 MVideoPlaylistVideosLength &
87 Use<'OwnerAccount', MAccountSummaryFormattable> & 98 Use<'OwnerAccount', MAccountSummaryFormattable> &
88 Use<'VideoChannel', MChannelSummaryFormattable> 99 Use<'VideoChannel', MChannelSummaryFormattable>
89 100
90export type MVideoPlaylistAP = MVideoPlaylist & 101export type MVideoPlaylistAP =
102 MVideoPlaylist &
91 Use<'Thumbnail', MThumbnail> & 103 Use<'Thumbnail', MThumbnail> &
92 Use<'VideoChannel', MChannelUrl> 104 Use<'VideoChannel', MChannelUrl>
diff --git a/server/typings/models/video/video-rate.ts b/server/typings/models/video/video-rate.ts
index f6bb527fc..64ce4965b 100644
--- a/server/typings/models/video/video-rate.ts
+++ b/server/typings/models/video/video-rate.ts
@@ -9,10 +9,12 @@ type Use<K extends keyof AccountVideoRateModel, M> = PickWith<AccountVideoRateMo
9 9
10export type MAccountVideoRate = Omit<AccountVideoRateModel, 'Video' | 'Account'> 10export type MAccountVideoRate = Omit<AccountVideoRateModel, 'Video' | 'Account'>
11 11
12export type MAccountVideoRateAccountUrl = MAccountVideoRate & 12export type MAccountVideoRateAccountUrl =
13 MAccountVideoRate &
13 Use<'Account', MAccountUrl> 14 Use<'Account', MAccountUrl>
14 15
15export type MAccountVideoRateAccountVideo = MAccountVideoRate & 16export type MAccountVideoRateAccountVideo =
17 MAccountVideoRate &
16 Use<'Account', MAccountAudience> & 18 Use<'Account', MAccountAudience> &
17 Use<'Video', MVideo> 19 Use<'Video', MVideo>
18 20
@@ -20,5 +22,6 @@ export type MAccountVideoRateAccountVideo = MAccountVideoRate &
20 22
21// Format for API or AP object 23// Format for API or AP object
22 24
23export type MAccountVideoRateFormattable = Pick<MAccountVideoRate, 'type'> & 25export type MAccountVideoRateFormattable =
26 Pick<MAccountVideoRate, 'type'> &
24 Use<'Video', MVideoFormattable> 27 Use<'Video', MVideoFormattable>
diff --git a/server/typings/models/video/video-redundancy.ts b/server/typings/models/video/video-redundancy.ts
index 25bdac057..5107aa7f4 100644
--- a/server/typings/models/video/video-redundancy.ts
+++ b/server/typings/models/video/video-redundancy.ts
@@ -16,16 +16,20 @@ export type MVideoRedundancyFileUrl = Pick<MVideoRedundancy, 'fileUrl'>
16 16
17// ############################################################################ 17// ############################################################################
18 18
19export type MVideoRedundancyFile = MVideoRedundancy & 19export type MVideoRedundancyFile =
20 MVideoRedundancy &
20 Use<'VideoFile', MVideoFile> 21 Use<'VideoFile', MVideoFile>
21 22
22export type MVideoRedundancyFileVideo = MVideoRedundancy & 23export type MVideoRedundancyFileVideo =
24 MVideoRedundancy &
23 Use<'VideoFile', MVideoFileVideo> 25 Use<'VideoFile', MVideoFileVideo>
24 26
25export type MVideoRedundancyStreamingPlaylistVideo = MVideoRedundancy & 27export type MVideoRedundancyStreamingPlaylistVideo =
28 MVideoRedundancy &
26 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> 29 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo>
27 30
28export type MVideoRedundancyVideo = MVideoRedundancy & 31export type MVideoRedundancyVideo =
32 MVideoRedundancy &
29 Use<'VideoFile', MVideoFileVideo> & 33 Use<'VideoFile', MVideoFileVideo> &
30 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> 34 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo>
31 35
@@ -33,6 +37,7 @@ export type MVideoRedundancyVideo = MVideoRedundancy &
33 37
34// Format for API or AP object 38// Format for API or AP object
35 39
36export type MVideoRedundancyAP = MVideoRedundancy & 40export type MVideoRedundancyAP =
41 MVideoRedundancy &
37 PickWithOpt<VideoRedundancyModel, 'VideoFile', MVideoFile & PickWith<VideoFileModel, 'Video', MVideoUrl>> & 42 PickWithOpt<VideoRedundancyModel, 'VideoFile', MVideoFile & PickWith<VideoFileModel, 'Video', MVideoUrl>> &
38 PickWithOpt<VideoRedundancyModel, 'VideoStreamingPlaylist', PickWith<VideoStreamingPlaylistModel, 'Video', MVideoUrl>> 43 PickWithOpt<VideoRedundancyModel, 'VideoStreamingPlaylist', PickWith<VideoStreamingPlaylistModel, 'Video', MVideoUrl>>
diff --git a/server/typings/models/video/video-share.ts b/server/typings/models/video/video-share.ts
index a7a90beeb..50ca75d26 100644
--- a/server/typings/models/video/video-share.ts
+++ b/server/typings/models/video/video-share.ts
@@ -9,9 +9,11 @@ type Use<K extends keyof VideoShareModel, M> = PickWith<VideoShareModel, K, M>
9 9
10export type MVideoShare = Omit<VideoShareModel, 'Actor' | 'Video'> 10export type MVideoShare = Omit<VideoShareModel, 'Actor' | 'Video'>
11 11
12export type MVideoShareActor = MVideoShare & 12export type MVideoShareActor =
13 MVideoShare &
13 Use<'Actor', MActorDefault> 14 Use<'Actor', MActorDefault>
14 15
15export type MVideoShareFull = MVideoShare & 16export type MVideoShareFull =
17 MVideoShare &
16 Use<'Actor', MActorDefault> & 18 Use<'Actor', MActorDefault> &
17 Use<'Video', MVideo> 19 Use<'Video', MVideo>
diff --git a/server/typings/models/video/video-streaming-playlist.ts b/server/typings/models/video/video-streaming-playlist.ts
index 436c0c072..3f54aa560 100644
--- a/server/typings/models/video/video-streaming-playlist.ts
+++ b/server/typings/models/video/video-streaming-playlist.ts
@@ -1,6 +1,6 @@
1import { VideoStreamingPlaylistModel } from '../../../models/video/video-streaming-playlist' 1import { VideoStreamingPlaylistModel } from '../../../models/video/video-streaming-playlist'
2import { PickWith, PickWithOpt } from '../../utils' 2import { PickWith, PickWithOpt } from '../../utils'
3import { MVideoRedundancyFileUrl } from './video-redundancy' 3import { MVideoRedundancyFileUrl, MVideoRedundancy } from './video-redundancy'
4import { MVideo } from './video' 4import { MVideo } from './video'
5import { MVideoFile } from './video-file' 5import { MVideoFile } from './video-file'
6 6
@@ -10,21 +10,31 @@ type Use<K extends keyof VideoStreamingPlaylistModel, M> = PickWith<VideoStreami
10 10
11export type MStreamingPlaylist = Omit<VideoStreamingPlaylistModel, 'Video' | 'RedundancyVideos' | 'VideoFiles'> 11export type MStreamingPlaylist = Omit<VideoStreamingPlaylistModel, 'Video' | 'RedundancyVideos' | 'VideoFiles'>
12 12
13export type MStreamingPlaylistFiles = MStreamingPlaylist & 13export type MStreamingPlaylistFiles =
14 MStreamingPlaylist &
14 Use<'VideoFiles', MVideoFile[]> 15 Use<'VideoFiles', MVideoFile[]>
15 16
16export type MStreamingPlaylistVideo = MStreamingPlaylist & 17export type MStreamingPlaylistVideo =
18 MStreamingPlaylist &
17 Use<'Video', MVideo> 19 Use<'Video', MVideo>
18 20
19export type MStreamingPlaylistFilesVideo = MStreamingPlaylist & 21export type MStreamingPlaylistFilesVideo =
22 MStreamingPlaylist &
20 Use<'VideoFiles', MVideoFile[]> & 23 Use<'VideoFiles', MVideoFile[]> &
21 Use<'Video', MVideo> 24 Use<'Video', MVideo>
22 25
23export type MStreamingPlaylistRedundancies = MStreamingPlaylist & 26export type MStreamingPlaylistRedundanciesAll =
27 MStreamingPlaylist &
28 Use<'VideoFiles', MVideoFile[]> &
29 Use<'RedundancyVideos', MVideoRedundancy[]>
30
31export type MStreamingPlaylistRedundancies =
32 MStreamingPlaylist &
24 Use<'VideoFiles', MVideoFile[]> & 33 Use<'VideoFiles', MVideoFile[]> &
25 Use<'RedundancyVideos', MVideoRedundancyFileUrl[]> 34 Use<'RedundancyVideos', MVideoRedundancyFileUrl[]>
26 35
27export type MStreamingPlaylistRedundanciesOpt = MStreamingPlaylist & 36export type MStreamingPlaylistRedundanciesOpt =
37 MStreamingPlaylist &
28 Use<'VideoFiles', MVideoFile[]> & 38 Use<'VideoFiles', MVideoFile[]> &
29 PickWithOpt<VideoStreamingPlaylistModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]> 39 PickWithOpt<VideoStreamingPlaylistModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]>
30 40
diff --git a/server/typings/models/video/video.ts b/server/typings/models/video/video.ts
index 7f69a91de..022a9566d 100644
--- a/server/typings/models/video/video.ts
+++ b/server/typings/models/video/video.ts
@@ -9,9 +9,14 @@ import {
9 MChannelUserId 9 MChannelUserId
10} from './video-channels' 10} from './video-channels'
11import { MTag } from './tag' 11import { MTag } from './tag'
12import { MVideoCaptionLanguage } from './video-caption' 12import { MVideoCaptionLanguage, MVideoCaptionLanguageUrl } from './video-caption'
13import { MStreamingPlaylistFiles, MStreamingPlaylistRedundancies, MStreamingPlaylistRedundanciesOpt } from './video-streaming-playlist' 13import {
14import { MVideoFile, MVideoFileRedundanciesOpt } from './video-file' 14 MStreamingPlaylistFiles,
15 MStreamingPlaylistRedundancies,
16 MStreamingPlaylistRedundanciesAll,
17 MStreamingPlaylistRedundanciesOpt
18} from './video-streaming-playlist'
19import { MVideoFile, MVideoFileRedundanciesAll, MVideoFileRedundanciesOpt } from './video-file'
15import { MThumbnail } from './thumbnail' 20import { MThumbnail } from './thumbnail'
16import { MVideoBlacklist, MVideoBlacklistLight, MVideoBlacklistUnfederated } from './video-blacklist' 21import { MVideoBlacklist, MVideoBlacklistLight, MVideoBlacklistUnfederated } from './video-blacklist'
17import { MScheduleVideoUpdate } from './schedule-video-update' 22import { MScheduleVideoUpdate } from './schedule-video-update'
@@ -21,7 +26,8 @@ type Use<K extends keyof VideoModel, M> = PickWith<VideoModel, K, M>
21 26
22// ############################################################################ 27// ############################################################################
23 28
24export type MVideo = Omit<VideoModel, 'VideoChannel' | 'Tags' | 'Thumbnails' | 'VideoPlaylistElements' | 'VideoAbuses' | 29export type MVideo =
30 Omit<VideoModel, 'VideoChannel' | 'Tags' | 'Thumbnails' | 'VideoPlaylistElements' | 'VideoAbuses' |
25 'VideoFiles' | 'VideoStreamingPlaylists' | 'VideoShares' | 'AccountVideoRates' | 'VideoComments' | 'VideoViews' | 'UserVideoHistories' | 31 'VideoFiles' | 'VideoStreamingPlaylists' | 'VideoShares' | 'AccountVideoRates' | 'VideoComments' | 'VideoViews' | 'UserVideoHistories' |
26 'ScheduleVideoUpdate' | 'VideoBlacklist' | 'VideoImport' | 'VideoCaptions'> 32 'ScheduleVideoUpdate' | 'VideoBlacklist' | 'VideoImport' | 'VideoCaptions'>
27 33
@@ -31,6 +37,7 @@ export type MVideoId = Pick<MVideo, 'id'>
31export type MVideoUrl = Pick<MVideo, 'url'> 37export type MVideoUrl = Pick<MVideo, 'url'>
32export type MVideoUUID = Pick<MVideo, 'uuid'> 38export type MVideoUUID = Pick<MVideo, 'uuid'>
33 39
40export type MVideoImmutable = Pick<MVideo, 'id' | 'url' | 'uuid' | 'remote' | 'isOwned'>
34export type MVideoIdUrl = MVideoId & MVideoUrl 41export type MVideoIdUrl = MVideoId & MVideoUrl
35export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'> 42export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'>
36 43
@@ -39,50 +46,63 @@ export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'>
39// Video raw associations: schedules, video files, tags, thumbnails, captions, streaming playlists 46// Video raw associations: schedules, video files, tags, thumbnails, captions, streaming playlists
40 47
41// "With" to not confuse with the VideoFile model 48// "With" to not confuse with the VideoFile model
42export type MVideoWithFile = MVideo & 49export type MVideoWithFile =
50 MVideo &
43 Use<'VideoFiles', MVideoFile[]> & 51 Use<'VideoFiles', MVideoFile[]> &
44 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> 52 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]>
45 53
46export type MVideoThumbnail = MVideo & 54export type MVideoThumbnail =
55 MVideo &
47 Use<'Thumbnails', MThumbnail[]> 56 Use<'Thumbnails', MThumbnail[]>
48 57
49export type MVideoIdThumbnail = MVideoId & 58export type MVideoIdThumbnail =
59 MVideoId &
50 Use<'Thumbnails', MThumbnail[]> 60 Use<'Thumbnails', MThumbnail[]>
51 61
52export type MVideoWithFileThumbnail = MVideo & 62export type MVideoWithFileThumbnail =
63 MVideo &
53 Use<'VideoFiles', MVideoFile[]> & 64 Use<'VideoFiles', MVideoFile[]> &
54 Use<'Thumbnails', MThumbnail[]> 65 Use<'Thumbnails', MThumbnail[]>
55 66
56export type MVideoThumbnailBlacklist = MVideo & 67export type MVideoThumbnailBlacklist =
68 MVideo &
57 Use<'Thumbnails', MThumbnail[]> & 69 Use<'Thumbnails', MThumbnail[]> &
58 Use<'VideoBlacklist', MVideoBlacklistLight> 70 Use<'VideoBlacklist', MVideoBlacklistLight>
59 71
60export type MVideoTag = MVideo & 72export type MVideoTag =
73 MVideo &
61 Use<'Tags', MTag[]> 74 Use<'Tags', MTag[]>
62 75
63export type MVideoWithSchedule = MVideo & 76export type MVideoWithSchedule =
77 MVideo &
64 PickWithOpt<VideoModel, 'ScheduleVideoUpdate', MScheduleVideoUpdate> 78 PickWithOpt<VideoModel, 'ScheduleVideoUpdate', MScheduleVideoUpdate>
65 79
66export type MVideoWithCaptions = MVideo & 80export type MVideoWithCaptions =
81 MVideo &
67 Use<'VideoCaptions', MVideoCaptionLanguage[]> 82 Use<'VideoCaptions', MVideoCaptionLanguage[]>
68 83
69export type MVideoWithStreamingPlaylist = MVideo & 84export type MVideoWithStreamingPlaylist =
85 MVideo &
70 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> 86 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]>
71 87
72// ############################################################################ 88// ############################################################################
73 89
74// Associations with not all their attributes 90// Associations with not all their attributes
75 91
76export type MVideoUserHistory = MVideo & 92export type MVideoUserHistory =
93 MVideo &
77 Use<'UserVideoHistories', MUserVideoHistoryTime[]> 94 Use<'UserVideoHistories', MUserVideoHistoryTime[]>
78 95
79export type MVideoWithBlacklistLight = MVideo & 96export type MVideoWithBlacklistLight =
97 MVideo &
80 Use<'VideoBlacklist', MVideoBlacklistLight> 98 Use<'VideoBlacklist', MVideoBlacklistLight>
81 99
82export type MVideoAccountLight = MVideo & 100export type MVideoAccountLight =
101 MVideo &
83 Use<'VideoChannel', MChannelAccountLight> 102 Use<'VideoChannel', MChannelAccountLight>
84 103
85export type MVideoWithRights = MVideo & 104export type MVideoWithRights =
105 MVideo &
86 Use<'VideoBlacklist', MVideoBlacklistLight> & 106 Use<'VideoBlacklist', MVideoBlacklistLight> &
87 Use<'Thumbnails', MThumbnail[]> & 107 Use<'Thumbnails', MThumbnail[]> &
88 Use<'VideoChannel', MChannelUserId> 108 Use<'VideoChannel', MChannelUserId>
@@ -91,12 +111,14 @@ export type MVideoWithRights = MVideo &
91 111
92// All files with some additional associations 112// All files with some additional associations
93 113
94export type MVideoWithAllFiles = MVideo & 114export type MVideoWithAllFiles =
115 MVideo &
95 Use<'VideoFiles', MVideoFile[]> & 116 Use<'VideoFiles', MVideoFile[]> &
96 Use<'Thumbnails', MThumbnail[]> & 117 Use<'Thumbnails', MThumbnail[]> &
97 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> 118 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]>
98 119
99export type MVideoAccountLightBlacklistAllFiles = MVideo & 120export type MVideoAccountLightBlacklistAllFiles =
121 MVideo &
100 Use<'VideoFiles', MVideoFile[]> & 122 Use<'VideoFiles', MVideoFile[]> &
101 Use<'Thumbnails', MThumbnail[]> & 123 Use<'Thumbnails', MThumbnail[]> &
102 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> & 124 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> &
@@ -107,17 +129,21 @@ export type MVideoAccountLightBlacklistAllFiles = MVideo &
107 129
108// With account 130// With account
109 131
110export type MVideoAccountDefault = MVideo & 132export type MVideoAccountDefault =
133 MVideo &
111 Use<'VideoChannel', MChannelAccountDefault> 134 Use<'VideoChannel', MChannelAccountDefault>
112 135
113export type MVideoThumbnailAccountDefault = MVideo & 136export type MVideoThumbnailAccountDefault =
137 MVideo &
114 Use<'Thumbnails', MThumbnail[]> & 138 Use<'Thumbnails', MThumbnail[]> &
115 Use<'VideoChannel', MChannelAccountDefault> 139 Use<'VideoChannel', MChannelAccountDefault>
116 140
117export type MVideoWithChannelActor = MVideo & 141export type MVideoWithChannelActor =
142 MVideo &
118 Use<'VideoChannel', MChannelActor> 143 Use<'VideoChannel', MChannelActor>
119 144
120export type MVideoFullLight = MVideo & 145export type MVideoFullLight =
146 MVideo &
121 Use<'Thumbnails', MThumbnail[]> & 147 Use<'Thumbnails', MThumbnail[]> &
122 Use<'VideoBlacklist', MVideoBlacklistLight> & 148 Use<'VideoBlacklist', MVideoBlacklistLight> &
123 Use<'Tags', MTag[]> & 149 Use<'Tags', MTag[]> &
@@ -131,18 +157,20 @@ export type MVideoFullLight = MVideo &
131 157
132// API 158// API
133 159
134export type MVideoAP = MVideo & 160export type MVideoAP =
161 MVideo &
135 Use<'Tags', MTag[]> & 162 Use<'Tags', MTag[]> &
136 Use<'VideoChannel', MChannelAccountLight> & 163 Use<'VideoChannel', MChannelAccountLight> &
137 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> & 164 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> &
138 Use<'VideoCaptions', MVideoCaptionLanguage[]> & 165 Use<'VideoCaptions', MVideoCaptionLanguageUrl[]> &
139 Use<'VideoBlacklist', MVideoBlacklistUnfederated> & 166 Use<'VideoBlacklist', MVideoBlacklistUnfederated> &
140 Use<'VideoFiles', MVideoFileRedundanciesOpt[]> & 167 Use<'VideoFiles', MVideoFileRedundanciesOpt[]> &
141 Use<'Thumbnails', MThumbnail[]> 168 Use<'Thumbnails', MThumbnail[]>
142 169
143export type MVideoAPWithoutCaption = Omit<MVideoAP, 'VideoCaptions'> 170export type MVideoAPWithoutCaption = Omit<MVideoAP, 'VideoCaptions'>
144 171
145export type MVideoDetails = MVideo & 172export type MVideoDetails =
173 MVideo &
146 Use<'VideoBlacklist', MVideoBlacklistLight> & 174 Use<'VideoBlacklist', MVideoBlacklistLight> &
147 Use<'Tags', MTag[]> & 175 Use<'Tags', MTag[]> &
148 Use<'VideoChannel', MChannelAccountLight> & 176 Use<'VideoChannel', MChannelAccountLight> &
@@ -152,23 +180,31 @@ export type MVideoDetails = MVideo &
152 Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundancies[]> & 180 Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundancies[]> &
153 Use<'VideoFiles', MVideoFileRedundanciesOpt[]> 181 Use<'VideoFiles', MVideoFileRedundanciesOpt[]>
154 182
155export type MVideoForUser = MVideo & 183export type MVideoForUser =
184 MVideo &
156 Use<'VideoChannel', MChannelAccountDefault> & 185 Use<'VideoChannel', MChannelAccountDefault> &
157 Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> & 186 Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> &
158 Use<'VideoBlacklist', MVideoBlacklistLight> & 187 Use<'VideoBlacklist', MVideoBlacklistLight> &
159 Use<'Thumbnails', MThumbnail[]> 188 Use<'Thumbnails', MThumbnail[]>
160 189
190export type MVideoForRedundancyAPI =
191 MVideo &
192 Use<'VideoFiles', MVideoFileRedundanciesAll[]> &
193 Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesAll[]>
194
161// ############################################################################ 195// ############################################################################
162 196
163// Format for API or AP object 197// Format for API or AP object
164 198
165export type MVideoFormattable = MVideo & 199export type MVideoFormattable =
200 MVideo &
166 PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> & 201 PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> &
167 Use<'VideoChannel', MChannelAccountSummaryFormattable> & 202 Use<'VideoChannel', MChannelAccountSummaryFormattable> &
168 PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> & 203 PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> &
169 PickWithOpt<VideoModel, 'VideoBlacklist', Pick<MVideoBlacklist, 'reason'>> 204 PickWithOpt<VideoModel, 'VideoBlacklist', Pick<MVideoBlacklist, 'reason'>>
170 205
171export type MVideoFormattableDetails = MVideoFormattable & 206export type MVideoFormattableDetails =
207 MVideoFormattable &
172 Use<'VideoChannel', MChannelFormattable> & 208 Use<'VideoChannel', MChannelFormattable> &
173 Use<'Tags', MTag[]> & 209 Use<'Tags', MTag[]> &
174 Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesOpt[]> & 210 Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesOpt[]> &
diff --git a/server/typings/utils.ts b/server/typings/utils.ts
index 24d43b258..55500d8c4 100644
--- a/server/typings/utils.ts
+++ b/server/typings/utils.ts
@@ -1,3 +1,5 @@
1/* eslint-disable @typescript-eslint/array-type */
2
1export type FunctionPropertyNames<T> = { 3export type FunctionPropertyNames<T> = {
2 [K in keyof T]: T[K] extends Function ? K : never 4 [K in keyof T]: T[K] extends Function ? K : never
3}[keyof T] 5}[keyof T]