aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.json1
-rw-r--r--CHANGELOG.md2
-rw-r--r--README.md23
-rw-r--r--client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts3
-rw-r--r--client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts3
-rw-r--r--client/src/app/+videos/+video-edit/shared/video-edit.component.ts11
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts10
-rw-r--r--client/src/app/+videos/+video-edit/video-add.component.html10
-rw-r--r--client/src/app/+videos/+video-edit/video-add.component.ts20
-rw-r--r--client/src/app/+videos/video-list/trending/video-trending-header.component.ts12
-rw-r--r--client/src/app/+videos/video-list/trending/video-trending.component.ts7
-rw-r--r--client/src/app/app.component.ts2
-rw-r--r--client/src/app/core/routing/redirect.service.ts35
-rw-r--r--client/src/app/core/server/server.service.ts25
-rw-r--r--client/src/app/core/theme/theme.service.ts14
-rw-r--r--client/src/index.html1
-rw-r--r--client/src/root-helpers/plugins.ts7
-rw-r--r--client/src/standalone/videos/embed.html12
-rw-r--r--client/src/standalone/videos/embed.ts52
-rw-r--r--scripts/optimize-old-videos.ts2
-rw-r--r--scripts/print-transcode-command.ts2
-rwxr-xr-xscripts/prune-storage.ts2
-rwxr-xr-xscripts/reset-password.ts2
-rwxr-xr-xscripts/update-host.ts4
-rw-r--r--server/controllers/activitypub/client.ts2
-rw-r--r--server/controllers/activitypub/utils.ts1
-rw-r--r--server/controllers/api/config.ts7
-rw-r--r--server/controllers/api/plugins.ts33
-rw-r--r--server/controllers/api/server/follows.ts18
-rw-r--r--server/controllers/api/server/server-blocklist.ts2
-rw-r--r--server/controllers/api/users/index.ts24
-rw-r--r--server/controllers/api/users/me.ts45
-rw-r--r--server/controllers/api/users/my-blocklist.ts2
-rw-r--r--server/controllers/api/users/my-history.ts2
-rw-r--r--server/controllers/api/users/my-notifications.ts14
-rw-r--r--server/controllers/api/users/my-subscriptions.ts2
-rw-r--r--server/controllers/api/video-channel.ts7
-rw-r--r--server/controllers/api/video-playlist.ts2
-rw-r--r--server/controllers/api/videos/comment.ts2
-rw-r--r--server/controllers/api/videos/import.ts170
-rw-r--r--server/controllers/api/videos/index.ts406
-rw-r--r--server/controllers/api/videos/ownership.ts4
-rw-r--r--server/controllers/api/videos/update.ts191
-rw-r--r--server/controllers/api/videos/upload.ts269
-rw-r--r--server/controllers/api/videos/watching.ts2
-rw-r--r--server/controllers/lazy-static.ts2
-rw-r--r--server/controllers/services.ts7
-rw-r--r--server/controllers/static.ts7
-rw-r--r--server/helpers/actor.ts2
-rw-r--r--server/helpers/audit-logger.ts2
-rw-r--r--server/helpers/custom-validators/misc.ts2
-rw-r--r--server/helpers/database-utils.ts4
-rw-r--r--server/helpers/express-utils.ts28
-rw-r--r--server/helpers/ffprobe-utils.ts3
-rw-r--r--server/helpers/middlewares/accounts.ts2
-rw-r--r--server/helpers/signup.ts2
-rw-r--r--server/helpers/webfinger.ts6
-rw-r--r--server/helpers/youtube-dl.ts553
-rw-r--r--server/initializers/checker-after-init.ts2
-rw-r--r--server/initializers/constants.ts3
-rw-r--r--server/initializers/database.ts14
-rw-r--r--server/initializers/installer.ts2
-rw-r--r--server/lib/activitypub/actor.ts7
-rw-r--r--server/lib/activitypub/audience.ts2
-rw-r--r--server/lib/activitypub/process/process-accept.ts4
-rw-r--r--server/lib/activitypub/process/process-delete.ts2
-rw-r--r--server/lib/activitypub/process/process-follow.ts14
-rw-r--r--server/lib/activitypub/process/process-reject.ts2
-rw-r--r--server/lib/activitypub/process/process-undo.ts4
-rw-r--r--server/lib/activitypub/process/process-update.ts20
-rw-r--r--server/lib/activitypub/send/send-delete.ts2
-rw-r--r--server/lib/activitypub/send/send-view.ts2
-rw-r--r--server/lib/activitypub/send/utils.ts12
-rw-r--r--server/lib/auth/oauth-model.ts4
-rw-r--r--server/lib/client-html.ts18
-rw-r--r--server/lib/config.ts45
-rw-r--r--server/lib/job-queue/handlers/activitypub-follow.ts20
-rw-r--r--server/lib/job-queue/handlers/activitypub-refresher.ts8
-rw-r--r--server/lib/job-queue/handlers/actor-keys.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.ts7
-rw-r--r--server/lib/job-queue/handlers/video-live-ending.ts2
-rw-r--r--server/lib/job-queue/handlers/video-transcoding.ts4
-rw-r--r--server/lib/job-queue/handlers/video-views.ts4
-rw-r--r--server/lib/live-manager.ts4
-rw-r--r--server/lib/moderation.ts6
-rw-r--r--server/lib/notifier.ts4
-rw-r--r--server/lib/plugins/hooks.ts6
-rw-r--r--server/lib/plugins/plugin-helpers-builder.ts2
-rw-r--r--server/lib/plugins/plugin-index.ts20
-rw-r--r--server/lib/plugins/plugin-manager.ts14
-rw-r--r--server/lib/plugins/register-helpers.ts6
-rw-r--r--server/lib/redundancy.ts12
-rw-r--r--server/lib/schedulers/actor-follow-scheduler.ts4
-rw-r--r--server/lib/schedulers/auto-follow-index-instances.ts2
-rw-r--r--server/lib/schedulers/remove-old-history-scheduler.ts2
-rw-r--r--server/lib/schedulers/youtube-dl-update-scheduler.ts6
-rw-r--r--server/lib/stat-manager.ts4
-rw-r--r--server/lib/transcoding/video-transcoding-profiles.ts (renamed from server/lib/video-transcoding-profiles.ts)8
-rw-r--r--server/lib/transcoding/video-transcoding.ts (renamed from server/lib/video-transcoding.ts)35
-rw-r--r--server/lib/user.ts9
-rw-r--r--server/lib/video-channel.ts4
-rw-r--r--server/lib/video-comment.ts2
-rw-r--r--server/lib/video.ts2
-rw-r--r--server/middlewares/validators/follows.ts16
-rw-r--r--server/middlewares/validators/plugins.ts14
-rw-r--r--server/middlewares/validators/user-subscriptions.ts8
-rw-r--r--server/middlewares/validators/users.ts4
-rw-r--r--server/middlewares/validators/videos/video-channels.ts4
-rw-r--r--server/middlewares/validators/videos/video-imports.ts3
-rw-r--r--server/middlewares/validators/videos/videos.ts2
-rw-r--r--server/middlewares/validators/webfinger.ts6
-rw-r--r--server/models/abuse/abuse-message.ts3
-rw-r--r--server/models/abuse/abuse.ts4
-rw-r--r--server/models/abuse/video-abuse.ts3
-rw-r--r--server/models/abuse/video-comment-abuse.ts3
-rw-r--r--server/models/account/account-blocklist.ts5
-rw-r--r--server/models/account/account-video-rate.ts5
-rw-r--r--server/models/account/account.ts13
-rw-r--r--server/models/actor/actor-follow.ts (renamed from server/models/activitypub/actor-follow.ts)5
-rw-r--r--server/models/actor/actor-image.ts (renamed from server/models/account/actor-image.ts)3
-rw-r--r--server/models/actor/actor.ts (renamed from server/models/activitypub/actor.ts)5
-rw-r--r--server/models/application/application.ts5
-rw-r--r--server/models/oauth/oauth-client.ts3
-rw-r--r--server/models/oauth/oauth-token.ts7
-rw-r--r--server/models/redundancy/video-redundancy.ts5
-rw-r--r--server/models/server/plugin.ts7
-rw-r--r--server/models/server/server-blocklist.ts3
-rw-r--r--server/models/server/server.ts5
-rw-r--r--server/models/server/tracker.ts3
-rw-r--r--server/models/server/video-tracker.ts3
-rw-r--r--server/models/user/user-notification-setting.ts (renamed from server/models/account/user-notification-setting.ts)3
-rw-r--r--server/models/user/user-notification.ts (renamed from server/models/account/user-notification.ts)11
-rw-r--r--server/models/user/user-video-history.ts (renamed from server/models/account/user-video-history.ts)7
-rw-r--r--server/models/user/user.ts (renamed from server/models/account/user.ts)11
-rw-r--r--server/models/utils.ts7
-rw-r--r--server/models/video/schedule-video-update.ts9
-rw-r--r--server/models/video/tag.ts3
-rw-r--r--server/models/video/thumbnail.ts3
-rw-r--r--server/models/video/video-blacklist.ts3
-rw-r--r--server/models/video/video-caption.ts3
-rw-r--r--server/models/video/video-change-ownership.ts3
-rw-r--r--server/models/video/video-channel.ts9
-rw-r--r--server/models/video/video-comment.ts7
-rw-r--r--server/models/video/video-file.ts3
-rw-r--r--server/models/video/video-import.ts7
-rw-r--r--server/models/video/video-live.ts3
-rw-r--r--server/models/video/video-playlist-element.ts6
-rw-r--r--server/models/video/video-playlist.ts5
-rw-r--r--server/models/video/video-query-builder.ts22
-rw-r--r--server/models/video/video-share.ts5
-rw-r--r--server/models/video/video-streaming-playlist.ts3
-rw-r--r--server/models/video/video-tag.ts3
-rw-r--r--server/models/video/video-view.ts5
-rw-r--r--server/models/video/video.ts17
-rw-r--r--server/tests/api/check-params/plugins.ts12
-rw-r--r--server/tests/api/moderation/blocklist.ts56
-rw-r--r--server/tests/api/notifications/comments-notifications.ts25
-rw-r--r--server/tests/api/server/bulk.ts9
-rw-r--r--server/tests/api/server/follows.ts48
-rw-r--r--server/tests/api/server/handle-down.ts12
-rw-r--r--server/tests/api/server/plugins.ts10
-rw-r--r--server/tests/api/videos/multiple-servers.ts13
-rw-r--r--server/tests/api/videos/video-comments.ts3
-rw-r--r--server/tests/client.ts28
-rw-r--r--server/tests/plugins/filter-hooks.ts2
-rw-r--r--server/tools/peertube-import-videos.ts25
-rw-r--r--server/tools/peertube-plugins.ts3
-rw-r--r--server/types/models/abuse/abuse-message.ts (renamed from server/types/models/moderation/abuse-message.ts)0
-rw-r--r--server/types/models/abuse/abuse.ts (renamed from server/types/models/moderation/abuse.ts)0
-rw-r--r--server/types/models/abuse/index.ts (renamed from server/types/models/moderation/index.ts)0
-rw-r--r--server/types/models/account/account.ts6
-rw-r--r--server/types/models/account/index.ts3
-rw-r--r--server/types/models/actor/actor-follow.ts (renamed from server/types/models/account/actor-follow.ts)2
-rw-r--r--server/types/models/actor/actor-image.ts (renamed from server/types/models/account/actor-image.ts)2
-rw-r--r--server/types/models/actor/actor.ts (renamed from server/types/models/account/actor.ts)5
-rw-r--r--server/types/models/actor/index.ts3
-rw-r--r--server/types/models/index.ts3
-rw-r--r--server/types/models/user/user-notification-setting.ts2
-rw-r--r--server/types/models/user/user-notification.ts8
-rw-r--r--server/types/models/user/user-video-history.ts2
-rw-r--r--server/types/models/user/user.ts2
-rw-r--r--server/types/models/video/video-channels.ts6
-rw-r--r--server/types/models/video/video-share.ts4
-rw-r--r--server/types/plugins/register-server-option.model.ts4
-rw-r--r--server/types/sequelize.ts5
-rw-r--r--shared/core-utils/miscs/types.ts4
-rw-r--r--shared/extra-utils/index.ts16
-rw-r--r--shared/extra-utils/server/plugins.ts4
-rw-r--r--shared/extra-utils/server/servers.ts3
-rw-r--r--shared/models/nodeinfo/index.ts1
-rw-r--r--shared/models/nodeinfo/nodeinfo.model.ts (renamed from shared/models/nodeinfo/index.d.ts)0
-rw-r--r--shared/models/overviews/index.ts2
-rw-r--r--shared/models/overviews/videos-overview.model.ts (renamed from shared/models/overviews/videos-overview.ts)0
-rw-r--r--shared/models/plugins/client/client-hook.model.ts (renamed from shared/models/plugins/client-hook.model.ts)0
-rw-r--r--shared/models/plugins/client/index.ts6
-rw-r--r--shared/models/plugins/client/plugin-client-scope.type.ts (renamed from shared/models/plugins/plugin-client-scope.type.ts)0
-rw-r--r--shared/models/plugins/client/plugin-element-placeholder.type.ts (renamed from shared/models/plugins/plugin-element-placeholder.type.ts)0
-rw-r--r--shared/models/plugins/client/register-client-form-field.model.ts (renamed from shared/models/plugins/register-client-form-field.model.ts)0
-rw-r--r--shared/models/plugins/client/register-client-hook.model.ts (renamed from shared/models/plugins/register-client-hook.model.ts)0
-rw-r--r--shared/models/plugins/client/register-client-settings-script.model.ts (renamed from shared/models/plugins/register-client-settings-script.model.ts)2
-rw-r--r--shared/models/plugins/index.ts28
-rw-r--r--shared/models/plugins/plugin-index/index.ts3
-rw-r--r--shared/models/plugins/plugin-index/peertube-plugin-index-list.model.ts (renamed from shared/models/plugins/peertube-plugin-index-list.model.ts)2
-rw-r--r--shared/models/plugins/plugin-index/peertube-plugin-index.model.ts (renamed from shared/models/plugins/peertube-plugin-index.model.ts)0
-rw-r--r--shared/models/plugins/plugin-index/peertube-plugin-latest-version.model.ts (renamed from shared/models/plugins/peertube-plugin-latest-version.model.ts)0
-rw-r--r--shared/models/plugins/plugin-package-json.model.ts2
-rw-r--r--shared/models/plugins/server/api/index.ts3
-rw-r--r--shared/models/plugins/server/api/install-plugin.model.ts (renamed from shared/models/plugins/install-plugin.model.ts)0
-rw-r--r--shared/models/plugins/server/api/manage-plugin.model.ts (renamed from shared/models/plugins/manage-plugin.model.ts)0
-rw-r--r--shared/models/plugins/server/api/peertube-plugin.model.ts (renamed from shared/models/plugins/peertube-plugin.model.ts)2
-rw-r--r--shared/models/plugins/server/index.ts6
-rw-r--r--shared/models/plugins/server/managers/index.ts9
-rw-r--r--shared/models/plugins/server/managers/plugin-playlist-privacy-manager.model.ts (renamed from shared/models/plugins/plugin-playlist-privacy-manager.model.ts)2
-rw-r--r--shared/models/plugins/server/managers/plugin-settings-manager.model.ts (renamed from shared/models/plugins/plugin-settings-manager.model.ts)0
-rw-r--r--shared/models/plugins/server/managers/plugin-storage-manager.model.ts (renamed from shared/models/plugins/plugin-storage-manager.model.ts)0
-rw-r--r--shared/models/plugins/server/managers/plugin-transcoding-manager.model.ts (renamed from shared/models/plugins/plugin-transcoding-manager.model.ts)2
-rw-r--r--shared/models/plugins/server/managers/plugin-video-category-manager.model.ts (renamed from shared/models/plugins/plugin-video-category-manager.model.ts)0
-rw-r--r--shared/models/plugins/server/managers/plugin-video-language-manager.model.ts (renamed from shared/models/plugins/plugin-video-language-manager.model.ts)0
-rw-r--r--shared/models/plugins/server/managers/plugin-video-licence-manager.model.ts (renamed from shared/models/plugins/plugin-video-licence-manager.model.ts)0
-rw-r--r--shared/models/plugins/server/managers/plugin-video-privacy-manager.model.ts (renamed from shared/models/plugins/plugin-video-privacy-manager.model.ts)2
-rw-r--r--shared/models/plugins/server/plugin-translation.model.ts (renamed from shared/models/plugins/plugin-translation.model.ts)0
-rw-r--r--shared/models/plugins/server/register-server-hook.model.ts (renamed from shared/models/plugins/register-server-hook.model.ts)0
-rw-r--r--shared/models/plugins/server/server-hook.model.ts (renamed from shared/models/plugins/server-hook.model.ts)0
-rw-r--r--shared/models/plugins/server/settings/index.ts2
-rw-r--r--shared/models/plugins/server/settings/public-server.setting.ts (renamed from shared/models/plugins/public-server.setting.ts)0
-rw-r--r--shared/models/plugins/server/settings/register-server-setting.model.ts (renamed from shared/models/plugins/register-server-setting.model.ts)2
-rw-r--r--shared/models/redundancy/index.ts3
-rw-r--r--shared/models/server/server-config.model.ts2
-rw-r--r--shared/models/server/server-error-code.enum.ts1
-rw-r--r--shared/models/videos/change-ownership/index.ts3
-rw-r--r--shared/models/videos/change-ownership/video-change-ownership-accept.model.ts (renamed from shared/models/videos/video-change-ownership-accept.model.ts)0
-rw-r--r--shared/models/videos/change-ownership/video-change-ownership-create.model.ts (renamed from shared/models/videos/video-change-ownership-create.model.ts)0
-rw-r--r--shared/models/videos/change-ownership/video-change-ownership.model.ts (renamed from shared/models/videos/video-change-ownership.model.ts)4
-rw-r--r--shared/models/videos/comment/index.ts1
-rw-r--r--shared/models/videos/comment/video-comment.model.ts (renamed from shared/models/videos/video-comment.model.ts)2
-rw-r--r--shared/models/videos/index.ts12
-rw-r--r--shared/models/videos/video-file-metadata.model.ts (renamed from shared/models/videos/video-file-metadata.ts)0
-rw-r--r--shared/models/videos/video-file.model.ts2
-rw-r--r--support/doc/api/openapi.yaml815
241 files changed, 2353 insertions, 1658 deletions
diff --git a/.eslintrc.json b/.eslintrc.json
index fa6fb1b6f..042254c95 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -88,6 +88,7 @@
88 "@typescript-eslint/no-namespace": "off", 88 "@typescript-eslint/no-namespace": "off",
89 "@typescript-eslint/no-empty-interface": "off", 89 "@typescript-eslint/no-empty-interface": "off",
90 "@typescript-eslint/no-extraneous-class": "off", 90 "@typescript-eslint/no-extraneous-class": "off",
91 "@typescript-eslint/no-use-before-define": "off",
91 // bugged but useful 92 // bugged but useful
92 "@typescript-eslint/restrict-plus-operands": "off" 93 "@typescript-eslint/restrict-plus-operands": "off"
93 }, 94 },
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6d9f09833..a37ee604d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -67,7 +67,7 @@
67 67
68### Features 68### Features
69 69
70 * :tada: Most robust uploads using a resumable upload endpoint [#3933](https://github.com/Chocobozzz/PeerTube/pull/3933) 70 * :tada: More robust uploads using a resumable upload endpoint [#3933](https://github.com/Chocobozzz/PeerTube/pull/3933)
71 * Accessibility/UI: 71 * Accessibility/UI:
72 * :tada: Redesign channel and account page 72 * :tada: Redesign channel and account page
73 * :tada: Increase video miniature size 73 * :tada: Increase video miniature size
diff --git a/README.md b/README.md
index f5fb6acea..cae96150e 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
6 6
7<p align=center> 7<p align=center>
8 <strong><a href="https://joinpeertube.org">Website</a></strong> 8 <strong><a href="https://joinpeertube.org">Website</a></strong>
9 | <strong><a href="https://instances.joinpeertube.org">Join an instance</a></strong> 9 | <strong><a href="https://joinpeertube.org/instances">Join an instance</a></strong>
10 | <strong><a href="#package-create-your-own-instance">Create an instance</a></strong> 10 | <strong><a href="#package-create-your-own-instance">Create an instance</a></strong>
11 | <strong><a href="#contact">Chat with us</a></strong> 11 | <strong><a href="#contact">Chat with us</a></strong>
12 | <strong><a href="https://framasoft.org/en/#soutenir">Donate</a></strong> 12 | <strong><a href="https://framasoft.org/en/#soutenir">Donate</a></strong>
@@ -67,23 +67,24 @@ Introduction
67 67
68PeerTube is a free, decentralized and federated video platform developed as an alternative to other platforms that centralize our data and attention, such as YouTube, Dailymotion or Vimeo. :clapper: 68PeerTube is a free, decentralized and federated video platform developed as an alternative to other platforms that centralize our data and attention, such as YouTube, Dailymotion or Vimeo. :clapper:
69 69
70But one organization hosting PeerTube alone may not have enough money to pay for bandwidth and video storage of its servers, 70To learn more:
71all servers of PeerTube are interoperable as a federated network, and non-PeerTube servers can be part of the larger Vidiverse
72(federated video network) by talking our implementation of ActivityPub.
73Video load is reduced thanks to P2P in the web browser using <a href="https://github.com/webtorrent/webtorrent">WebTorrent</a> or <a href="https://github.com/novage/p2p-media-loader">p2p-media-loader</a>.
74
75To learn more, see:
76* This [two-minute video](https://framatube.org/videos/watch/217eefeb-883d-45be-b7fc-a788ad8507d3) (hosted on PeerTube) explaining what PeerTube is and how it works 71* This [two-minute video](https://framatube.org/videos/watch/217eefeb-883d-45be-b7fc-a788ad8507d3) (hosted on PeerTube) explaining what PeerTube is and how it works
77* PeerTube's project homepage, [joinpeertube.org](https://joinpeertube.org) 72* PeerTube's project homepage, [joinpeertube.org](https://joinpeertube.org)
78* Demonstration instances: 73* Demonstration instances:
79 * [peertube.cpy.re](https://peertube.cpy.re) 74 * [peertube.cpy.re](https://peertube.cpy.re) (stable)
80 * [peertube2.cpy.re](https://peertube2.cpy.re) 75 * [peertube2.cpy.re](https://peertube2.cpy.re) (Nightly)
81 * [peertube3.cpy.re](https://peertube3.cpy.re) 76 * [peertube3.cpy.re](https://peertube3.cpy.re) (RC)
82* This [video](https://peertube.cpy.re/videos/watch/da2b08d4-a242-4170-b32a-4ec8cbdca701) demonstrating the communication between PeerTube and [Mastodon](https://github.com/tootsuite/mastodon) (a decentralized Twitter alternative) 77* This [video](https://peertube.cpy.re/videos/watch/da2b08d4-a242-4170-b32a-4ec8cbdca701) demonstrating the communication between PeerTube and [Mastodon](https://github.com/tootsuite/mastodon) (a decentralized Twitter alternative)
83 78
84:sparkles: Features 79:sparkles: Features
85---------------------------------------------------------------- 80----------------------------------------------------------------
86 81
82<p align=center>
83 <strong><a href="https://joinpeertube.org/faq#what-are-the-peertube-features-for-viewers">All features for viewers</a></strong>
84 | <strong><a href="https://joinpeertube.org/faq#what-are-the-peertube-features-for-content-creators">All features for content creators</a></strong>
85 | <strong><a href="https://joinpeertube.org/faq#what-are-the-peertube-features-for-administrators">All features for administrators</a></strong>
86</p>
87
87<img src="https://lutim.cpy.re/AHbctLjn.png" align="left" height="300px"/> 88<img src="https://lutim.cpy.re/AHbctLjn.png" align="left" height="300px"/>
88<h3 align="left">Video streaming, even in live!</h3> 89<h3 align="left">Video streaming, even in live!</h3>
89<p align="left"> 90<p align="left">
@@ -121,6 +122,8 @@ In addition to visitors using WebTorrent to share the load among them, instances
121Content creators can get help from their viewers in the simplest way possible: a support button showing a message linking to their donation accounts or really anything else. No more pay-per-view and advertisements that hurt visitors and <strike>incentivize</strike> alter creativity (more about that in our <a href="https://github.com/Chocobozzz/PeerTube/blob/develop/FAQ.md">FAQ</a>). 122Content creators can get help from their viewers in the simplest way possible: a support button showing a message linking to their donation accounts or really anything else. No more pay-per-view and advertisements that hurt visitors and <strike>incentivize</strike> alter creativity (more about that in our <a href="https://github.com/Chocobozzz/PeerTube/blob/develop/FAQ.md">FAQ</a>).
122</p> 123</p>
123 124
125
126
124:raised_hands: Contributing 127:raised_hands: Contributing
125---------------------------------------------------------------- 128----------------------------------------------------------------
126 129
diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
index 1a95980ae..6af224920 100644
--- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
+++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
@@ -5,8 +5,7 @@ import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
5import { ComponentPagination, ConfirmService, hasMoreItems, Notifier } from '@app/core' 5import { ComponentPagination, ConfirmService, hasMoreItems, Notifier } from '@app/core'
6import { PluginService } from '@app/core/plugins/plugin.service' 6import { PluginService } from '@app/core/plugins/plugin.service'
7import { compareSemVer } from '@shared/core-utils/miscs/miscs' 7import { compareSemVer } from '@shared/core-utils/miscs/miscs'
8import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model' 8import { PeerTubePlugin, PluginType } from '@shared/models'
9import { PluginType } from '@shared/models/plugins/plugin.type'
10 9
11@Component({ 10@Component({
12 selector: 'my-plugin-list-installed', 11 selector: 'my-plugin-list-installed',
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
index d2c179aba..0a6e57904 100644
--- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
+++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
@@ -4,8 +4,7 @@ import { Component, OnInit } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' 5import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
6import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core' 6import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core'
7import { PeerTubePluginIndex } from '@shared/models/plugins/peertube-plugin-index.model' 7import { PeerTubePluginIndex, PluginType } from '@shared/models'
8import { PluginType } from '@shared/models/plugins/plugin.type'
9 8
10@Component({ 9@Component({
11 selector: 'my-plugin-search', 10 selector: 'my-plugin-search',
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts
index 34119f7ab..3d916dbce 100644
--- a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts
+++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts
@@ -21,8 +21,15 @@ import {
21import { FormReactiveValidationMessages, FormValidatorService } from '@app/shared/shared-forms' 21import { FormReactiveValidationMessages, FormValidatorService } from '@app/shared/shared-forms'
22import { InstanceService } from '@app/shared/shared-instance' 22import { InstanceService } from '@app/shared/shared-instance'
23import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main' 23import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main'
24import { LiveVideo, ServerConfig, VideoConstant, VideoDetails, VideoPrivacy } from '@shared/models' 24import {
25import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model' 25 LiveVideo,
26 RegisterClientFormFieldOptions,
27 RegisterClientVideoFieldOptions,
28 ServerConfig,
29 VideoConstant,
30 VideoDetails,
31 VideoPrivacy
32} from '@shared/models'
26import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' 33import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service'
27import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' 34import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component'
28import { VideoEditType } from './video-edit.type' 35import { VideoEditType } from './video-edit.type'
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
index 3aae24732..23bd5ef76 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
@@ -5,7 +5,7 @@ import { scrollToTop } from '@app/helpers'
5import { FormValidatorService } from '@app/shared/shared-forms' 5import { FormValidatorService } from '@app/shared/shared-forms'
6import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' 6import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
7import { LoadingBarService } from '@ngx-loading-bar/core' 7import { LoadingBarService } from '@ngx-loading-bar/core'
8import { VideoPrivacy, VideoUpdate } from '@shared/models' 8import { ServerErrorCode, VideoPrivacy, VideoUpdate } from '@shared/models'
9import { hydrateFormFromVideo } from '../shared/video-edit-utils' 9import { hydrateFormFromVideo } from '../shared/video-edit-utils'
10import { VideoSend } from './video-send' 10import { VideoSend } from './video-send'
11 11
@@ -113,7 +113,13 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Af
113 this.loadingBar.useRef().complete() 113 this.loadingBar.useRef().complete()
114 this.isImportingVideo = false 114 this.isImportingVideo = false
115 this.firstStepError.emit() 115 this.firstStepError.emit()
116 this.notifier.error(err.message) 116
117 let message = err.message
118 if (err.body?.code === ServerErrorCode.INCORRECT_FILES_IN_TORRENT) {
119 message = $localize`Torrents with only 1 file are supported.`
120 }
121
122 this.notifier.error(message)
117 } 123 }
118 ) 124 )
119 } 125 }
diff --git a/client/src/app/+videos/+video-edit/video-add.component.html b/client/src/app/+videos/+video-edit/video-add.component.html
index dc8c2f21d..ac75d9ff8 100644
--- a/client/src/app/+videos/+video-edit/video-add.component.html
+++ b/client/src/app/+videos/+video-edit/video-add.component.html
@@ -20,8 +20,8 @@
20 <ng-container *ngIf="secondStepType === 'upload'" i18n>Upload {{ videoName }}</ng-container> 20 <ng-container *ngIf="secondStepType === 'upload'" i18n>Upload {{ videoName }}</ng-container>
21 </div> 21 </div>
22 22
23 <div ngbNav #nav="ngbNav" class="nav-tabs video-add-nav" [ngClass]="{ 'hide-nav': secondStepType !== undefined }"> 23 <div ngbNav #nav="ngbNav" class="nav-tabs video-add-nav" [activeId]="activeNav" (activeIdChange)="onNavChange($event)" [ngClass]="{ 'hide-nav': !!secondStepType }">
24 <ng-container ngbNavItem> 24 <ng-container ngbNavItem="upload">
25 <a ngbNavLink> 25 <a ngbNavLink>
26 <span i18n>Upload a file</span> 26 <span i18n>Upload a file</span>
27 </a> 27 </a>
@@ -31,7 +31,7 @@
31 </ng-template> 31 </ng-template>
32 </ng-container> 32 </ng-container>
33 33
34 <ng-container ngbNavItem *ngIf="isVideoImportHttpEnabled()"> 34 <ng-container ngbNavItem="import-url" *ngIf="isVideoImportHttpEnabled()">
35 <a ngbNavLink> 35 <a ngbNavLink>
36 <span i18n>Import with URL</span> 36 <span i18n>Import with URL</span>
37 </a> 37 </a>
@@ -41,7 +41,7 @@
41 </ng-template> 41 </ng-template>
42 </ng-container> 42 </ng-container>
43 43
44 <ng-container ngbNavItem *ngIf="isVideoImportTorrentEnabled()"> 44 <ng-container ngbNavItem="import-torrent" *ngIf="isVideoImportTorrentEnabled()">
45 <a ngbNavLink> 45 <a ngbNavLink>
46 <span i18n>Import with torrent</span> 46 <span i18n>Import with torrent</span>
47 </a> 47 </a>
@@ -51,7 +51,7 @@
51 </ng-template> 51 </ng-template>
52 </ng-container> 52 </ng-container>
53 53
54 <ng-container ngbNavItem *ngIf="isVideoLiveEnabled()"> 54 <ng-container ngbNavItem="go-live" *ngIf="isVideoLiveEnabled()">
55 <a ngbNavLink> 55 <a ngbNavLink>
56 <span i18n>Go live</span> 56 <span i18n>Go live</span>
57 </a> 57 </a>
diff --git a/client/src/app/+videos/+video-edit/video-add.component.ts b/client/src/app/+videos/+video-edit/video-add.component.ts
index 441d5a3db..d735c936c 100644
--- a/client/src/app/+videos/+video-edit/video-add.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add.component.ts
@@ -1,4 +1,5 @@
1import { Component, HostListener, OnInit, ViewChild } from '@angular/core' 1import { Component, HostListener, OnInit, ViewChild } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'
2import { AuthService, AuthUser, CanComponentDeactivate, ServerService } from '@app/core' 3import { AuthService, AuthUser, CanComponentDeactivate, ServerService } from '@app/core'
3import { ServerConfig } from '@shared/models' 4import { ServerConfig } from '@shared/models'
4import { VideoEditType } from './shared/video-edit.type' 5import { VideoEditType } from './shared/video-edit.type'
@@ -22,11 +23,16 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate {
22 23
23 secondStepType: VideoEditType 24 secondStepType: VideoEditType
24 videoName: string 25 videoName: string
25 serverConfig: ServerConfig 26
27 activeNav: string
28
29 private serverConfig: ServerConfig
26 30
27 constructor ( 31 constructor (
28 private auth: AuthService, 32 private auth: AuthService,
29 private serverService: ServerService 33 private serverService: ServerService,
34 private route: ActivatedRoute,
35 private router: Router
30 ) {} 36 ) {}
31 37
32 get userInformationLoaded () { 38 get userInformationLoaded () {
@@ -42,6 +48,16 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate {
42 .subscribe(config => this.serverConfig = config) 48 .subscribe(config => this.serverConfig = config)
43 49
44 this.user = this.auth.getUser() 50 this.user = this.auth.getUser()
51
52 if (this.route.snapshot.fragment) {
53 this.onNavChange(this.route.snapshot.fragment)
54 }
55 }
56
57 onNavChange (newActiveNav: string) {
58 this.activeNav = newActiveNav
59
60 this.router.navigate([], { fragment: this.activeNav })
45 } 61 }
46 62
47 onFirstStepDone (type: VideoEditType, videoName: string) { 63 onFirstStepDone (type: VideoEditType, videoName: string) {
diff --git a/client/src/app/+videos/video-list/trending/video-trending-header.component.ts b/client/src/app/+videos/video-list/trending/video-trending-header.component.ts
index 55040f3c9..bbb02a236 100644
--- a/client/src/app/+videos/video-list/trending/video-trending-header.component.ts
+++ b/client/src/app/+videos/video-list/trending/video-trending-header.component.ts
@@ -31,7 +31,8 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent imple
31 private route: ActivatedRoute, 31 private route: ActivatedRoute,
32 private router: Router, 32 private router: Router,
33 private auth: AuthService, 33 private auth: AuthService,
34 private serverService: ServerService 34 private serverService: ServerService,
35 private redirectService: RedirectService
35 ) { 36 ) {
36 super(data) 37 super(data)
37 38
@@ -84,12 +85,7 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent imple
84 85
85 this.algorithmChangeSub = this.route.queryParams.subscribe( 86 this.algorithmChangeSub = this.route.queryParams.subscribe(
86 queryParams => { 87 queryParams => {
87 const algorithm = queryParams['alg'] 88 this.data.model = queryParams['alg'] || this.redirectService.getDefaultTrendingAlgorithm()
88 if (algorithm) {
89 this.data.model = algorithm
90 } else {
91 this.data.model = RedirectService.DEFAULT_TRENDING_ALGORITHM
92 }
93 } 89 }
94 ) 90 )
95 } 91 }
@@ -99,7 +95,7 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent imple
99 } 95 }
100 96
101 setSort () { 97 setSort () {
102 const alg = this.data.model !== RedirectService.DEFAULT_TRENDING_ALGORITHM 98 const alg = this.data.model !== this.redirectService.getDefaultTrendingAlgorithm()
103 ? this.data.model 99 ? this.data.model
104 : undefined 100 : undefined
105 101
diff --git a/client/src/app/+videos/video-list/trending/video-trending.component.ts b/client/src/app/+videos/video-list/trending/video-trending.component.ts
index e50d6ec3a..ebec672f3 100644
--- a/client/src/app/+videos/video-list/trending/video-trending.component.ts
+++ b/client/src/app/+videos/video-list/trending/video-trending.component.ts
@@ -35,11 +35,12 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
35 protected storageService: LocalStorageService, 35 protected storageService: LocalStorageService,
36 protected cfr: ComponentFactoryResolver, 36 protected cfr: ComponentFactoryResolver,
37 private videoService: VideoService, 37 private videoService: VideoService,
38 private redirectService: RedirectService,
38 private hooks: HooksService 39 private hooks: HooksService
39 ) { 40 ) {
40 super() 41 super()
41 42
42 this.defaultSort = this.parseAlgorithm(RedirectService.DEFAULT_TRENDING_ALGORITHM) 43 this.defaultSort = this.parseAlgorithm(this.redirectService.getDefaultTrendingAlgorithm())
43 44
44 this.headerComponentInjector = this.getInjector() 45 this.headerComponentInjector = this.getInjector()
45 } 46 }
@@ -106,7 +107,7 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
106 } 107 }
107 108
108 protected loadPageRouteParams (queryParams: Params) { 109 protected loadPageRouteParams (queryParams: Params) {
109 const algorithm = queryParams['alg'] || RedirectService.DEFAULT_TRENDING_ALGORITHM 110 const algorithm = queryParams['alg'] || this.redirectService.getDefaultTrendingAlgorithm()
110 111
111 this.sort = this.parseAlgorithm(algorithm) 112 this.sort = this.parseAlgorithm(algorithm)
112 } 113 }
@@ -115,8 +116,10 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
115 switch (algorithm) { 116 switch (algorithm) {
116 case 'most-viewed': 117 case 'most-viewed':
117 return '-trending' 118 return '-trending'
119
118 case 'most-liked': 120 case 'most-liked':
119 return '-likes' 121 return '-likes'
122
120 default: 123 default:
121 return '-' + algorithm as VideoSortField 124 return '-' + algorithm as VideoSortField
122 } 125 }
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index 66d871b4a..239e275a4 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -67,7 +67,7 @@ export class AppComponent implements OnInit, AfterViewInit {
67 } 67 }
68 68
69 goToDefaultRoute () { 69 goToDefaultRoute () {
70 return this.router.navigateByUrl(RedirectService.DEFAULT_ROUTE) 70 return this.router.navigateByUrl(this.redirectService.getDefaultRoute())
71 } 71 }
72 72
73 ngOnInit () { 73 ngOnInit () {
diff --git a/client/src/app/core/routing/redirect.service.ts b/client/src/app/core/routing/redirect.service.ts
index 6d26fb504..cf690a4d0 100644
--- a/client/src/app/core/routing/redirect.service.ts
+++ b/client/src/app/core/routing/redirect.service.ts
@@ -6,14 +6,14 @@ import { ServerService } from '../server'
6export class RedirectService { 6export class RedirectService {
7 // Default route could change according to the instance configuration 7 // Default route could change according to the instance configuration
8 static INIT_DEFAULT_ROUTE = '/videos/trending' 8 static INIT_DEFAULT_ROUTE = '/videos/trending'
9 static DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE
10 static INIT_DEFAULT_TRENDING_ALGORITHM = 'most-viewed' 9 static INIT_DEFAULT_TRENDING_ALGORITHM = 'most-viewed'
11 static DEFAULT_TRENDING_ALGORITHM = RedirectService.INIT_DEFAULT_TRENDING_ALGORITHM
12 10
13 private previousUrl: string 11 private previousUrl: string
14 private currentUrl: string 12 private currentUrl: string
15 13
16 private redirectingToHomepage = false 14 private redirectingToHomepage = false
15 private defaultTrendingAlgorithm = RedirectService.INIT_DEFAULT_TRENDING_ALGORITHM
16 private defaultRoute = RedirectService.INIT_DEFAULT_ROUTE
17 17
18 constructor ( 18 constructor (
19 private router: Router, 19 private router: Router,
@@ -22,10 +22,10 @@ export class RedirectService {
22 // The config is first loaded from the cache so try to get the default route 22 // The config is first loaded from the cache so try to get the default route
23 const tmpConfig = this.serverService.getTmpConfig() 23 const tmpConfig = this.serverService.getTmpConfig()
24 if (tmpConfig?.instance?.defaultClientRoute) { 24 if (tmpConfig?.instance?.defaultClientRoute) {
25 RedirectService.DEFAULT_ROUTE = tmpConfig.instance.defaultClientRoute 25 this.defaultRoute = tmpConfig.instance.defaultClientRoute
26 } 26 }
27 if (tmpConfig?.trending?.videos?.algorithms?.default) { 27 if (tmpConfig?.trending?.videos?.algorithms?.default) {
28 RedirectService.DEFAULT_TRENDING_ALGORITHM = tmpConfig.trending.videos.algorithms.default 28 this.defaultTrendingAlgorithm = tmpConfig.trending.videos.algorithms.default
29 } 29 }
30 30
31 // Load default route 31 // Load default route
@@ -34,13 +34,8 @@ export class RedirectService {
34 const defaultRouteConfig = config.instance.defaultClientRoute 34 const defaultRouteConfig = config.instance.defaultClientRoute
35 const defaultTrendingConfig = config.trending.videos.algorithms.default 35 const defaultTrendingConfig = config.trending.videos.algorithms.default
36 36
37 if (defaultRouteConfig) { 37 if (defaultRouteConfig) this.defaultRoute = defaultRouteConfig
38 RedirectService.DEFAULT_ROUTE = defaultRouteConfig 38 if (defaultTrendingConfig) this.defaultTrendingAlgorithm = defaultTrendingConfig
39 }
40
41 if (defaultTrendingConfig) {
42 RedirectService.DEFAULT_TRENDING_ALGORITHM = defaultTrendingConfig
43 }
44 }) 39 })
45 40
46 // Track previous url 41 // Track previous url
@@ -53,6 +48,14 @@ export class RedirectService {
53 }) 48 })
54 } 49 }
55 50
51 getDefaultRoute () {
52 return this.defaultRoute
53 }
54
55 getDefaultTrendingAlgorithm () {
56 return this.defaultTrendingAlgorithm
57 }
58
56 redirectToPreviousRoute () { 59 redirectToPreviousRoute () {
57 const exceptions = [ 60 const exceptions = [
58 '/verify-account', 61 '/verify-account',
@@ -72,21 +75,21 @@ export class RedirectService {
72 75
73 this.redirectingToHomepage = true 76 this.redirectingToHomepage = true
74 77
75 console.log('Redirecting to %s...', RedirectService.DEFAULT_ROUTE) 78 console.log('Redirecting to %s...', this.defaultRoute)
76 79
77 this.router.navigateByUrl(RedirectService.DEFAULT_ROUTE, { skipLocationChange }) 80 this.router.navigateByUrl(this.defaultRoute, { skipLocationChange })
78 .then(() => this.redirectingToHomepage = false) 81 .then(() => this.redirectingToHomepage = false)
79 .catch(() => { 82 .catch(() => {
80 this.redirectingToHomepage = false 83 this.redirectingToHomepage = false
81 84
82 console.error( 85 console.error(
83 'Cannot navigate to %s, resetting default route to %s.', 86 'Cannot navigate to %s, resetting default route to %s.',
84 RedirectService.DEFAULT_ROUTE, 87 this.defaultRoute,
85 RedirectService.INIT_DEFAULT_ROUTE 88 RedirectService.INIT_DEFAULT_ROUTE
86 ) 89 )
87 90
88 RedirectService.DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE 91 this.defaultRoute = RedirectService.INIT_DEFAULT_ROUTE
89 return this.router.navigateByUrl(RedirectService.DEFAULT_ROUTE, { skipLocationChange }) 92 return this.router.navigateByUrl(this.defaultRoute, { skipLocationChange })
90 }) 93 })
91 94
92 } 95 }
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index 906191ae1..e48786e18 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -3,7 +3,6 @@ import { first, map, share, shareReplay, switchMap, tap } from 'rxjs/operators'
3import { HttpClient } from '@angular/common/http' 3import { HttpClient } from '@angular/common/http'
4import { Inject, Injectable, LOCALE_ID } from '@angular/core' 4import { Inject, Injectable, LOCALE_ID } from '@angular/core'
5import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers' 5import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers'
6import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
7import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' 6import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
8import { SearchTargetType, ServerConfig, ServerStats, VideoConstant } from '@shared/models' 7import { SearchTargetType, ServerConfig, ServerStats, VideoConstant } from '@shared/models'
9import { environment } from '../../../environments/environment' 8import { environment } from '../../../environments/environment'
@@ -16,8 +15,6 @@ export class ServerService {
16 private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/' 15 private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/'
17 private static BASE_STATS_URL = environment.apiUrl + '/api/v1/server/stats' 16 private static BASE_STATS_URL = environment.apiUrl + '/api/v1/server/stats'
18 17
19 private static CONFIG_LOCAL_STORAGE_KEY = 'server-config'
20
21 configReloaded = new Subject<ServerConfig>() 18 configReloaded = new Subject<ServerConfig>()
22 19
23 private localeObservable: Observable<any> 20 private localeObservable: Observable<any>
@@ -212,7 +209,6 @@ export class ServerService {
212 if (!this.configObservable) { 209 if (!this.configObservable) {
213 this.configObservable = this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL) 210 this.configObservable = this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL)
214 .pipe( 211 .pipe(
215 tap(config => this.saveConfigLocally(config)),
216 tap(config => { 212 tap(config => {
217 this.config = config 213 this.config = config
218 this.configLoaded = true 214 this.configLoaded = true
@@ -343,20 +339,15 @@ export class ServerService {
343 ) 339 )
344 } 340 }
345 341
346 private saveConfigLocally (config: ServerConfig) {
347 peertubeLocalStorage.setItem(ServerService.CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config))
348 }
349
350 private loadConfigLocally () { 342 private loadConfigLocally () {
351 const configString = peertubeLocalStorage.getItem(ServerService.CONFIG_LOCAL_STORAGE_KEY) 343 const configString = window['PeerTubeServerConfig']
352 344 if (!configString) return
353 if (configString) { 345
354 try { 346 try {
355 const parsed = JSON.parse(configString) 347 const parsed = JSON.parse(configString)
356 Object.assign(this.config, parsed) 348 Object.assign(this.config, parsed)
357 } catch (err) { 349 } catch (err) {
358 console.error('Cannot parse config saved in local storage.', err) 350 console.error('Cannot parse config saved in from index.html.', err)
359 }
360 } 351 }
361 } 352 }
362} 353}
diff --git a/client/src/app/core/theme/theme.service.ts b/client/src/app/core/theme/theme.service.ts
index 4c4611d01..e7a5ae17a 100644
--- a/client/src/app/core/theme/theme.service.ts
+++ b/client/src/app/core/theme/theme.service.ts
@@ -82,7 +82,19 @@ export class ThemeService {
82 : this.userService.getAnonymousUser().theme 82 : this.userService.getAnonymousUser().theme
83 83
84 if (theme !== 'instance-default') return theme 84 if (theme !== 'instance-default') return theme
85 return this.serverConfig.theme.default 85
86 const instanceTheme = this.serverConfig.theme.default
87 if (instanceTheme !== 'default') return instanceTheme
88
89 // Default to dark theme if available and wanted by the user
90 if (
91 this.themes.find(t => t.name === 'dark') &&
92 window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
93 ) {
94 return 'dark'
95 }
96
97 return instanceTheme
86 } 98 }
87 99
88 private loadTheme (name: string) { 100 private loadTheme (name: string) {
diff --git a/client/src/index.html b/client/src/index.html
index 72c184dc1..28667cdd0 100644
--- a/client/src/index.html
+++ b/client/src/index.html
@@ -29,6 +29,7 @@
29 <!-- description tag --> 29 <!-- description tag -->
30 <!-- custom css tag --> 30 <!-- custom css tag -->
31 <!-- meta tags --> 31 <!-- meta tags -->
32 <!-- server config -->
32 33
33 <!-- /!\ Do not remove it /!\ --> 34 <!-- /!\ Do not remove it /!\ -->
34 </head> 35 </head>
diff --git a/client/src/root-helpers/plugins.ts b/client/src/root-helpers/plugins.ts
index 5344c0468..8c1c858b7 100644
--- a/client/src/root-helpers/plugins.ts
+++ b/client/src/root-helpers/plugins.ts
@@ -1,14 +1,15 @@
1import { RegisterClientHelpers } from 'src/types/register-client-option.model' 1import { RegisterClientHelpers } from 'src/types/register-client-option.model'
2import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' 2import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
3import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model'
4import { 3import {
5 ClientHookName, 4 ClientHookName,
6 clientHookObject, 5 clientHookObject,
7 ClientScript, 6 ClientScript,
8 PluginType, 7 PluginType,
8 RegisterClientFormFieldOptions,
9 RegisterClientHookOptions, 9 RegisterClientHookOptions,
10 ServerConfigPlugin, 10 RegisterClientSettingsScript,
11 RegisterClientSettingsScript 11 RegisterClientVideoFieldOptions,
12 ServerConfigPlugin
12} from '../../../shared/models' 13} from '../../../shared/models'
13import { ClientScript as ClientScriptModule } from '../types/client-script.model' 14import { ClientScript as ClientScriptModule } from '../types/client-script.model'
14import { importModule } from './utils' 15import { importModule } from './utils'
diff --git a/client/src/standalone/videos/embed.html b/client/src/standalone/videos/embed.html
index 7d09bfb8f..e13a4dc24 100644
--- a/client/src/standalone/videos/embed.html
+++ b/client/src/standalone/videos/embed.html
@@ -1,14 +1,22 @@
1<!DOCTYPE html> 1<!DOCTYPE html>
2<html> 2<html>
3 <head> 3 <head>
4 <!-- title tag -->
5
6 <meta charset="UTF-8"> 4 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width, initial-scale=1"> 5 <meta name="viewport" content="width=device-width, initial-scale=1">
8 <meta name="robots" content="noindex"> 6 <meta name="robots" content="noindex">
9 <meta property="og:platform" content="PeerTube" /> 7 <meta property="og:platform" content="PeerTube" />
10 8
9
10 <!-- /!\ The following comment is used by the server to prerender some tags /!\ -->
11
12 <!-- title tag -->
13 <!-- description tag -->
11 <!-- custom css tag --> 14 <!-- custom css tag -->
15 <!-- meta tags -->
16 <!-- server config -->
17
18 <!-- /!\ Do not remove it /!\ -->
19
12 <link rel="icon" type="image/png" href="/client/assets/images/favicon.png" /> 20 <link rel="icon" type="image/png" href="/client/assets/images/favicon.png" />
13 </head> 21 </head>
14 22
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index 3a90fdc58..fc61d3730 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -1,28 +1,28 @@
1import './embed.scss' 1import './embed.scss'
2import videojs from 'video.js' 2import videojs from 'video.js'
3import { peertubeTranslate } from '../../../../shared/core-utils/i18n' 3import { peertubeTranslate } from '../../../../shared/core-utils/i18n'
4import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
4import { 5import {
6 ClientHookName,
7 HTMLServerConfig,
8 PluginType,
5 ResultList, 9 ResultList,
6 ServerConfig,
7 UserRefreshToken, 10 UserRefreshToken,
8 VideoCaption, 11 VideoCaption,
9 VideoDetails, 12 VideoDetails,
10 VideoPlaylist, 13 VideoPlaylist,
11 VideoPlaylistElement, 14 VideoPlaylistElement,
12 VideoStreamingPlaylistType, 15 VideoStreamingPlaylistType
13 PluginType,
14 ClientHookName
15} from '../../../../shared/models' 16} from '../../../../shared/models'
16import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
17import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager' 17import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager'
18import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' 18import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
19import { TranslationsManager } from '../../assets/player/translations-manager' 19import { TranslationsManager } from '../../assets/player/translations-manager'
20import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage'
20import { Hooks, loadPlugin, runHook } from '../../root-helpers/plugins' 21import { Hooks, loadPlugin, runHook } from '../../root-helpers/plugins'
21import { Tokens } from '../../root-helpers/users' 22import { Tokens } from '../../root-helpers/users'
22import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage'
23import { objectToUrlEncoded } from '../../root-helpers/utils' 23import { objectToUrlEncoded } from '../../root-helpers/utils'
24import { PeerTubeEmbedApi } from './embed-api'
25import { RegisterClientHelpers } from '../../types/register-client-option.model' 24import { RegisterClientHelpers } from '../../types/register-client-option.model'
25import { PeerTubeEmbedApi } from './embed-api'
26 26
27type Translations = { [ id: string ]: string } 27type Translations = { [ id: string ]: string }
28 28
@@ -56,8 +56,9 @@ export class PeerTubeEmbed {
56 CLIENT_SECRET: 'client_secret' 56 CLIENT_SECRET: 'client_secret'
57 } 57 }
58 58
59 config: HTMLServerConfig
60
59 private translationsPromise: Promise<{ [id: string]: string }> 61 private translationsPromise: Promise<{ [id: string]: string }>
60 private configPromise: Promise<ServerConfig>
61 private PeertubePlayerManagerModulePromise: Promise<any> 62 private PeertubePlayerManagerModulePromise: Promise<any>
62 63
63 private playlist: VideoPlaylist 64 private playlist: VideoPlaylist
@@ -77,6 +78,12 @@ export class PeerTubeEmbed {
77 78
78 constructor (private videoWrapperId: string) { 79 constructor (private videoWrapperId: string) {
79 this.wrapperElement = document.getElementById(this.videoWrapperId) 80 this.wrapperElement = document.getElementById(this.videoWrapperId)
81
82 try {
83 this.config = JSON.parse(window['PeerTubeServerConfig'])
84 } catch (err) {
85 console.error('Cannot parse HTML config.', err)
86 }
80 } 87 }
81 88
82 getVideoUrl (id: string) { 89 getVideoUrl (id: string) {
@@ -166,11 +173,6 @@ export class PeerTubeEmbed {
166 return this.refreshFetch(url.toString(), { headers: this.headers }) 173 return this.refreshFetch(url.toString(), { headers: this.headers })
167 } 174 }
168 175
169 loadConfig (): Promise<ServerConfig> {
170 return this.refreshFetch('/api/v1/config')
171 .then(res => res.json())
172 }
173
174 removeElement (element: HTMLElement) { 176 removeElement (element: HTMLElement) {
175 element.parentElement.removeChild(element) 177 element.parentElement.removeChild(element)
176 } 178 }
@@ -466,6 +468,12 @@ export class PeerTubeEmbed {
466 this.playerElement.setAttribute('playsinline', 'true') 468 this.playerElement.setAttribute('playsinline', 'true')
467 this.wrapperElement.appendChild(this.playerElement) 469 this.wrapperElement.appendChild(this.playerElement)
468 470
471 // Issue when we parsed config from HTML, fallback to API
472 if (!this.config) {
473 this.config = await this.refreshFetch('/api/v1/config')
474 .then(res => res.json())
475 }
476
469 const videoInfoPromise = videoResponse.json() 477 const videoInfoPromise = videoResponse.json()
470 .then((videoInfo: VideoDetails) => { 478 .then((videoInfo: VideoDetails) => {
471 if (!alreadyHadPlayer) this.loadPlaceholder(videoInfo) 479 if (!alreadyHadPlayer) this.loadPlaceholder(videoInfo)
@@ -473,15 +481,14 @@ export class PeerTubeEmbed {
473 return videoInfo 481 return videoInfo
474 }) 482 })
475 483
476 const [ videoInfoTmp, serverTranslations, captionsResponse, config, PeertubePlayerManagerModule ] = await Promise.all([ 484 const [ videoInfoTmp, serverTranslations, captionsResponse, PeertubePlayerManagerModule ] = await Promise.all([
477 videoInfoPromise, 485 videoInfoPromise,
478 this.translationsPromise, 486 this.translationsPromise,
479 captionsPromise, 487 captionsPromise,
480 this.configPromise,
481 this.PeertubePlayerManagerModulePromise 488 this.PeertubePlayerManagerModulePromise
482 ]) 489 ])
483 490
484 await this.ensurePluginsAreLoaded(config, serverTranslations) 491 await this.ensurePluginsAreLoaded(serverTranslations)
485 492
486 const videoInfo: VideoDetails = videoInfoTmp 493 const videoInfo: VideoDetails = videoInfoTmp
487 494
@@ -576,7 +583,7 @@ export class PeerTubeEmbed {
576 583
577 this.buildCSS() 584 this.buildCSS()
578 585
579 await this.buildDock(videoInfo, config) 586 await this.buildDock(videoInfo)
580 587
581 this.initializeApi() 588 this.initializeApi()
582 589
@@ -598,7 +605,6 @@ export class PeerTubeEmbed {
598 private async initCore () { 605 private async initCore () {
599 if (this.userTokens) this.setHeadersFromTokens() 606 if (this.userTokens) this.setHeadersFromTokens()
600 607
601 this.configPromise = this.loadConfig()
602 this.translationsPromise = TranslationsManager.getServerTranslations(window.location.origin, navigator.language) 608 this.translationsPromise = TranslationsManager.getServerTranslations(window.location.origin, navigator.language)
603 this.PeertubePlayerManagerModulePromise = import('../../assets/player/peertube-player-manager') 609 this.PeertubePlayerManagerModulePromise = import('../../assets/player/peertube-player-manager')
604 610
@@ -653,7 +659,7 @@ export class PeerTubeEmbed {
653 } 659 }
654 } 660 }
655 661
656 private async buildDock (videoInfo: VideoDetails, config: ServerConfig) { 662 private async buildDock (videoInfo: VideoDetails) {
657 if (!this.controls) return 663 if (!this.controls) return
658 664
659 // On webtorrent fallback, player may have been disposed 665 // On webtorrent fallback, player may have been disposed
@@ -661,7 +667,7 @@ export class PeerTubeEmbed {
661 667
662 const title = this.title ? videoInfo.name : undefined 668 const title = this.title ? videoInfo.name : undefined
663 669
664 const description = config.tracker.enabled && this.warningTitle 670 const description = this.config.tracker.enabled && this.warningTitle
665 ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>' 671 ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>'
666 : undefined 672 : undefined
667 673
@@ -733,10 +739,10 @@ export class PeerTubeEmbed {
733 return window.location.pathname.split('/')[1] === 'video-playlists' 739 return window.location.pathname.split('/')[1] === 'video-playlists'
734 } 740 }
735 741
736 private async ensurePluginsAreLoaded (config: ServerConfig, translations?: { [ id: string ]: string }) { 742 private async ensurePluginsAreLoaded (translations?: { [ id: string ]: string }) {
737 if (config.plugin.registered.length === 0) return 743 if (this.config.plugin.registered.length === 0) return
738 744
739 for (const plugin of config.plugin.registered) { 745 for (const plugin of this.config.plugin.registered) {
740 for (const key of Object.keys(plugin.clientScripts)) { 746 for (const key of Object.keys(plugin.clientScripts)) {
741 const clientScript = plugin.clientScripts[key] 747 const clientScript = plugin.clientScripts[key]
742 748
diff --git a/scripts/optimize-old-videos.ts b/scripts/optimize-old-videos.ts
index 01d30244f..9692d76ba 100644
--- a/scripts/optimize-old-videos.ts
+++ b/scripts/optimize-old-videos.ts
@@ -5,7 +5,7 @@ import { VIDEO_TRANSCODING_FPS } from '../server/initializers/constants'
5import { getDurationFromVideoFile, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../server/helpers/ffprobe-utils' 5import { getDurationFromVideoFile, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../server/helpers/ffprobe-utils'
6import { getMaxBitrate } from '../shared/models/videos' 6import { getMaxBitrate } from '../shared/models/videos'
7import { VideoModel } from '../server/models/video/video' 7import { VideoModel } from '../server/models/video/video'
8import { optimizeOriginalVideofile } from '../server/lib/video-transcoding' 8import { optimizeOriginalVideofile } from '../server/lib/transcoding/video-transcoding'
9import { initDatabaseModels } from '../server/initializers/database' 9import { initDatabaseModels } from '../server/initializers/database'
10import { basename, dirname } from 'path' 10import { basename, dirname } from 'path'
11import { copy, move, remove } from 'fs-extra' 11import { copy, move, remove } from 'fs-extra'
diff --git a/scripts/print-transcode-command.ts b/scripts/print-transcode-command.ts
index f6c96790e..00ac9ab6c 100644
--- a/scripts/print-transcode-command.ts
+++ b/scripts/print-transcode-command.ts
@@ -5,7 +5,7 @@ import * as program from 'commander'
5import * as ffmpeg from 'fluent-ffmpeg' 5import * as ffmpeg from 'fluent-ffmpeg'
6import { buildx264VODCommand, runCommand, TranscodeOptions } from '@server/helpers/ffmpeg-utils' 6import { buildx264VODCommand, runCommand, TranscodeOptions } from '@server/helpers/ffmpeg-utils'
7import { exit } from 'process' 7import { exit } from 'process'
8import { VideoTranscodingProfilesManager } from '@server/lib/video-transcoding-profiles' 8import { VideoTranscodingProfilesManager } from '@server/lib/transcoding/video-transcoding-profiles'
9 9
10program 10program
11 .arguments('<path>') 11 .arguments('<path>')
diff --git a/scripts/prune-storage.ts b/scripts/prune-storage.ts
index 32314b0b7..0f2d1320e 100755
--- a/scripts/prune-storage.ts
+++ b/scripts/prune-storage.ts
@@ -11,7 +11,7 @@ import { VideoRedundancyModel } from '../server/models/redundancy/video-redundan
11import * as Bluebird from 'bluebird' 11import * as Bluebird from 'bluebird'
12import { getUUIDFromFilename } from '../server/helpers/utils' 12import { getUUIDFromFilename } from '../server/helpers/utils'
13import { ThumbnailModel } from '../server/models/video/thumbnail' 13import { ThumbnailModel } from '../server/models/video/thumbnail'
14import { ActorImageModel } from '../server/models/account/actor-image' 14import { ActorImageModel } from '../server/models/actor/actor-image'
15import { uniq, values } from 'lodash' 15import { uniq, values } from 'lodash'
16import { ThumbnailType } from '@shared/models' 16import { ThumbnailType } from '@shared/models'
17 17
diff --git a/scripts/reset-password.ts b/scripts/reset-password.ts
index 7e7de6b8a..7c1a64a3f 100755
--- a/scripts/reset-password.ts
+++ b/scripts/reset-password.ts
@@ -3,7 +3,7 @@ registerTSPaths()
3 3
4import * as program from 'commander' 4import * as program from 'commander'
5import { initDatabaseModels } from '../server/initializers/database' 5import { initDatabaseModels } from '../server/initializers/database'
6import { UserModel } from '../server/models/account/user' 6import { UserModel } from '../server/models/user/user'
7import { isUserPasswordValid } from '../server/helpers/custom-validators/users' 7import { isUserPasswordValid } from '../server/helpers/custom-validators/users'
8 8
9program 9program
diff --git a/scripts/update-host.ts b/scripts/update-host.ts
index e497be4e2..592684225 100755
--- a/scripts/update-host.ts
+++ b/scripts/update-host.ts
@@ -2,9 +2,9 @@ import { registerTSPaths } from '../server/helpers/register-ts-paths'
2registerTSPaths() 2registerTSPaths()
3 3
4import { WEBSERVER } from '../server/initializers/constants' 4import { WEBSERVER } from '../server/initializers/constants'
5import { ActorFollowModel } from '../server/models/activitypub/actor-follow' 5import { ActorFollowModel } from '../server/models/actor/actor-follow'
6import { VideoModel } from '../server/models/video/video' 6import { VideoModel } from '../server/models/video/video'
7import { ActorModel } from '../server/models/activitypub/actor' 7import { ActorModel } from '../server/models/actor/actor'
8import { 8import {
9 getLocalAccountActivityPubUrl, 9 getLocalAccountActivityPubUrl,
10 getLocalVideoActivityPubUrl, 10 getLocalVideoActivityPubUrl,
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index 1b4acc234..1982e171d 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -30,7 +30,7 @@ import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator }
30import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' 30import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists'
31import { AccountModel } from '../../models/account/account' 31import { AccountModel } from '../../models/account/account'
32import { AccountVideoRateModel } from '../../models/account/account-video-rate' 32import { AccountVideoRateModel } from '../../models/account/account-video-rate'
33import { ActorFollowModel } from '../../models/activitypub/actor-follow' 33import { ActorFollowModel } from '../../models/actor/actor-follow'
34import { VideoModel } from '../../models/video/video' 34import { VideoModel } from '../../models/video/video'
35import { VideoCaptionModel } from '../../models/video/video-caption' 35import { VideoCaptionModel } from '../../models/video/video-caption'
36import { VideoCommentModel } from '../../models/video/video-comment' 36import { VideoCommentModel } from '../../models/video/video-comment'
diff --git a/server/controllers/activitypub/utils.ts b/server/controllers/activitypub/utils.ts
index 599cf48ab..19bdd58eb 100644
--- a/server/controllers/activitypub/utils.ts
+++ b/server/controllers/activitypub/utils.ts
@@ -3,7 +3,6 @@ import * as express from 'express'
3function activityPubResponse (data: any, res: express.Response) { 3function activityPubResponse (data: any, res: express.Response) {
4 return res.type('application/activity+json; charset=utf-8') 4 return res.type('application/activity+json; charset=utf-8')
5 .json(data) 5 .json(data)
6 .end()
7} 6}
8 7
9export { 8export {
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 2ddb73519..5ce7adc35 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -18,6 +18,7 @@ const configRouter = express.Router()
18const auditLogger = auditLoggerFactory('config') 18const auditLogger = auditLoggerFactory('config')
19 19
20configRouter.get('/about', getAbout) 20configRouter.get('/about', getAbout)
21
21configRouter.get('/', 22configRouter.get('/',
22 asyncMiddleware(getConfig) 23 asyncMiddleware(getConfig)
23) 24)
@@ -27,12 +28,14 @@ configRouter.get('/custom',
27 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), 28 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
28 getCustomConfig 29 getCustomConfig
29) 30)
31
30configRouter.put('/custom', 32configRouter.put('/custom',
31 authenticate, 33 authenticate,
32 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), 34 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
33 customConfigUpdateValidator, 35 customConfigUpdateValidator,
34 asyncMiddleware(updateCustomConfig) 36 asyncMiddleware(updateCustomConfig)
35) 37)
38
36configRouter.delete('/custom', 39configRouter.delete('/custom',
37 authenticate, 40 authenticate,
38 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), 41 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
@@ -67,13 +70,13 @@ function getAbout (req: express.Request, res: express.Response) {
67 } 70 }
68 } 71 }
69 72
70 return res.json(about).end() 73 return res.json(about)
71} 74}
72 75
73function getCustomConfig (req: express.Request, res: express.Response) { 76function getCustomConfig (req: express.Request, res: express.Response) {
74 const data = customConfig() 77 const data = customConfig()
75 78
76 return res.json(data).end() 79 return res.json(data)
77} 80}
78 81
79async function deleteCustomConfig (req: express.Request, res: express.Response) { 82async function deleteCustomConfig (req: express.Request, res: express.Response) {
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts
index a186de010..e18eed332 100644
--- a/server/controllers/api/plugins.ts
+++ b/server/controllers/api/plugins.ts
@@ -1,16 +1,18 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getFormattedObjects } from '../../helpers/utils' 2import { logger } from '@server/helpers/logger'
3import { getFormattedObjects } from '@server/helpers/utils'
4import { listAvailablePluginsFromIndex } from '@server/lib/plugins/plugin-index'
5import { PluginManager } from '@server/lib/plugins/plugin-manager'
3import { 6import {
4 asyncMiddleware, 7 asyncMiddleware,
5 authenticate, 8 authenticate,
9 availablePluginsSortValidator,
6 ensureUserHasRight, 10 ensureUserHasRight,
7 paginationValidator, 11 paginationValidator,
12 pluginsSortValidator,
8 setDefaultPagination, 13 setDefaultPagination,
9 setDefaultSort 14 setDefaultSort
10} from '../../middlewares' 15} from '@server/middlewares'
11import { availablePluginsSortValidator, pluginsSortValidator } from '../../middlewares/validators'
12import { PluginModel } from '../../models/server/plugin'
13import { UserRight } from '../../../shared/models/users'
14import { 16import {
15 existingPluginValidator, 17 existingPluginValidator,
16 installOrUpdatePluginValidator, 18 installOrUpdatePluginValidator,
@@ -18,16 +20,17 @@ import {
18 listPluginsValidator, 20 listPluginsValidator,
19 uninstallPluginValidator, 21 uninstallPluginValidator,
20 updatePluginSettingsValidator 22 updatePluginSettingsValidator
21} from '../../middlewares/validators/plugins' 23} from '@server/middlewares/validators/plugins'
22import { PluginManager } from '../../lib/plugins/plugin-manager' 24import { PluginModel } from '@server/models/server/plugin'
23import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model' 25import { HttpStatusCode } from '@shared/core-utils'
24import { ManagePlugin } from '../../../shared/models/plugins/manage-plugin.model' 26import {
25import { logger } from '../../helpers/logger' 27 InstallOrUpdatePlugin,
26import { listAvailablePluginsFromIndex } from '../../lib/plugins/plugin-index' 28 ManagePlugin,
27import { PeertubePluginIndexList } from '../../../shared/models/plugins/peertube-plugin-index-list.model' 29 PeertubePluginIndexList,
28import { RegisteredServerSettings } from '../../../shared/models/plugins/register-server-setting.model' 30 PublicServerSetting,
29import { PublicServerSetting } from '../../../shared/models/plugins/public-server.setting' 31 RegisteredServerSettings,
30import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' 32 UserRight
33} from '@shared/models'
31 34
32const pluginRouter = express.Router() 35const pluginRouter = express.Router()
33 36
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts
index 80025bc5b..daeef22de 100644
--- a/server/controllers/api/server/follows.ts
+++ b/server/controllers/api/server/follows.ts
@@ -1,9 +1,15 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getServerActor } from '@server/models/application/application'
3import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
2import { UserRight } from '../../../../shared/models/users' 4import { UserRight } from '../../../../shared/models/users'
3import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
4import { getFormattedObjects } from '../../../helpers/utils' 6import { getFormattedObjects } from '../../../helpers/utils'
5import { SERVER_ACTOR_NAME } from '../../../initializers/constants' 7import { SERVER_ACTOR_NAME } from '../../../initializers/constants'
8import { sequelizeTypescript } from '../../../initializers/database'
9import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow'
6import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send' 10import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send'
11import { JobQueue } from '../../../lib/job-queue'
12import { removeRedundanciesOfServer } from '../../../lib/redundancy'
7import { 13import {
8 asyncMiddleware, 14 asyncMiddleware,
9 authenticate, 15 authenticate,
@@ -19,16 +25,10 @@ import {
19 followingSortValidator, 25 followingSortValidator,
20 followValidator, 26 followValidator,
21 getFollowerValidator, 27 getFollowerValidator,
22 removeFollowingValidator, 28 listFollowsValidator,
23 listFollowsValidator 29 removeFollowingValidator
24} from '../../../middlewares/validators' 30} from '../../../middlewares/validators'
25import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 31import { ActorFollowModel } from '../../../models/actor/actor-follow'
26import { JobQueue } from '../../../lib/job-queue'
27import { removeRedundanciesOfServer } from '../../../lib/redundancy'
28import { sequelizeTypescript } from '../../../initializers/database'
29import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow'
30import { getServerActor } from '@server/models/application/application'
31import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
32 32
33const serverFollowsRouter = express.Router() 33const serverFollowsRouter = express.Router()
34serverFollowsRouter.get('/following', 34serverFollowsRouter.get('/following',
diff --git a/server/controllers/api/server/server-blocklist.ts b/server/controllers/api/server/server-blocklist.ts
index 6e341c0fb..a86bc7d19 100644
--- a/server/controllers/api/server/server-blocklist.ts
+++ b/server/controllers/api/server/server-blocklist.ts
@@ -1,7 +1,7 @@
1import 'multer' 1import 'multer'
2import * as express from 'express' 2import * as express from 'express'
3import { logger } from '@server/helpers/logger' 3import { logger } from '@server/helpers/logger'
4import { UserNotificationModel } from '@server/models/account/user-notification' 4import { UserNotificationModel } from '@server/models/user/user-notification'
5import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
6import { UserRight } from '../../../../shared/models/users' 6import { UserRight } from '../../../../shared/models/users'
7import { getFormattedObjects } from '../../../helpers/utils' 7import { getFormattedObjects } from '../../../helpers/utils'
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index e2b1ea7cd..f384f0f28 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -45,7 +45,7 @@ import {
45 usersResetPasswordValidator, 45 usersResetPasswordValidator,
46 usersVerifyEmailValidator 46 usersVerifyEmailValidator
47} from '../../../middlewares/validators' 47} from '../../../middlewares/validators'
48import { UserModel } from '../../../models/account/user' 48import { UserModel } from '../../../models/user/user'
49import { meRouter } from './me' 49import { meRouter } from './me'
50import { myAbusesRouter } from './my-abuses' 50import { myAbusesRouter } from './my-abuses'
51import { myBlocklistRouter } from './my-blocklist' 51import { myBlocklistRouter } from './my-blocklist'
@@ -323,14 +323,20 @@ async function updateUser (req: express.Request, res: express.Response) {
323 const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON()) 323 const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON())
324 const roleChanged = body.role !== undefined && body.role !== userToUpdate.role 324 const roleChanged = body.role !== undefined && body.role !== userToUpdate.role
325 325
326 if (body.password !== undefined) userToUpdate.password = body.password 326 const keysToUpdate: (keyof UserUpdate)[] = [
327 if (body.email !== undefined) userToUpdate.email = body.email 327 'password',
328 if (body.emailVerified !== undefined) userToUpdate.emailVerified = body.emailVerified 328 'email',
329 if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota 329 'emailVerified',
330 if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily 330 'videoQuota',
331 if (body.role !== undefined) userToUpdate.role = body.role 331 'videoQuotaDaily',
332 if (body.adminFlags !== undefined) userToUpdate.adminFlags = body.adminFlags 332 'role',
333 if (body.pluginAuth !== undefined) userToUpdate.pluginAuth = body.pluginAuth 333 'adminFlags',
334 'pluginAuth'
335 ]
336
337 for (const key of keysToUpdate) {
338 if (body[key] !== undefined) userToUpdate.set(key, body[key])
339 }
334 340
335 const user = await userToUpdate.save() 341 const user = await userToUpdate.save()
336 342
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 0763d1900..a609abaa6 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -28,9 +28,10 @@ import { deleteMeValidator, videoImportsSortValidator, videosSortValidator } fro
28import { updateAvatarValidator } from '../../../middlewares/validators/actor-image' 28import { updateAvatarValidator } from '../../../middlewares/validators/actor-image'
29import { AccountModel } from '../../../models/account/account' 29import { AccountModel } from '../../../models/account/account'
30import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 30import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
31import { UserModel } from '../../../models/account/user' 31import { UserModel } from '../../../models/user/user'
32import { VideoModel } from '../../../models/video/video' 32import { VideoModel } from '../../../models/video/video'
33import { VideoImportModel } from '../../../models/video/video-import' 33import { VideoImportModel } from '../../../models/video/video-import'
34import { AttributesOnly } from '@shared/core-utils'
34 35
35const auditLogger = auditLoggerFactory('users') 36const auditLogger = auditLoggerFactory('users')
36 37
@@ -191,17 +192,23 @@ async function updateMe (req: express.Request, res: express.Response) {
191 192
192 const user = res.locals.oauth.token.user 193 const user = res.locals.oauth.token.user
193 194
194 if (body.password !== undefined) user.password = body.password 195 const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [
195 if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy 196 'password',
196 if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled 197 'nsfwPolicy',
197 if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo 198 'webTorrentEnabled',
198 if (body.autoPlayNextVideo !== undefined) user.autoPlayNextVideo = body.autoPlayNextVideo 199 'autoPlayVideo',
199 if (body.autoPlayNextVideoPlaylist !== undefined) user.autoPlayNextVideoPlaylist = body.autoPlayNextVideoPlaylist 200 'autoPlayNextVideo',
200 if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled 201 'autoPlayNextVideoPlaylist',
201 if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages 202 'videosHistoryEnabled',
202 if (body.theme !== undefined) user.theme = body.theme 203 'videoLanguages',
203 if (body.noInstanceConfigWarningModal !== undefined) user.noInstanceConfigWarningModal = body.noInstanceConfigWarningModal 204 'theme',
204 if (body.noWelcomeModal !== undefined) user.noWelcomeModal = body.noWelcomeModal 205 'noInstanceConfigWarningModal',
206 'noWelcomeModal'
207 ]
208
209 for (const key of keysToUpdate) {
210 if (body[key] !== undefined) user.set(key, body[key])
211 }
205 212
206 if (body.email !== undefined) { 213 if (body.email !== undefined) {
207 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { 214 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
@@ -215,15 +222,15 @@ async function updateMe (req: express.Request, res: express.Response) {
215 await sequelizeTypescript.transaction(async t => { 222 await sequelizeTypescript.transaction(async t => {
216 await user.save({ transaction: t }) 223 await user.save({ transaction: t })
217 224
218 if (body.displayName !== undefined || body.description !== undefined) { 225 if (body.displayName === undefined && body.description === undefined) return
219 const userAccount = await AccountModel.load(user.Account.id, t)
220 226
221 if (body.displayName !== undefined) userAccount.name = body.displayName 227 const userAccount = await AccountModel.load(user.Account.id, t)
222 if (body.description !== undefined) userAccount.description = body.description
223 await userAccount.save({ transaction: t })
224 228
225 await sendUpdateActor(userAccount, t) 229 if (body.displayName !== undefined) userAccount.name = body.displayName
226 } 230 if (body.description !== undefined) userAccount.description = body.description
231 await userAccount.save({ transaction: t })
232
233 await sendUpdateActor(userAccount, t)
227 }) 234 })
228 235
229 if (sendVerificationEmail === true) { 236 if (sendVerificationEmail === true) {
diff --git a/server/controllers/api/users/my-blocklist.ts b/server/controllers/api/users/my-blocklist.ts
index faaef3ac0..a1561b751 100644
--- a/server/controllers/api/users/my-blocklist.ts
+++ b/server/controllers/api/users/my-blocklist.ts
@@ -20,7 +20,7 @@ import {
20import { AccountBlocklistModel } from '../../../models/account/account-blocklist' 20import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
21import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' 21import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
22import { ServerBlocklistModel } from '../../../models/server/server-blocklist' 22import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
23import { UserNotificationModel } from '@server/models/account/user-notification' 23import { UserNotificationModel } from '@server/models/user/user-notification'
24import { logger } from '@server/helpers/logger' 24import { logger } from '@server/helpers/logger'
25import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 25import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
26 26
diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts
index 72c7da373..cff1697ab 100644
--- a/server/controllers/api/users/my-history.ts
+++ b/server/controllers/api/users/my-history.ts
@@ -9,7 +9,7 @@ import {
9 userHistoryRemoveValidator 9 userHistoryRemoveValidator
10} from '../../../middlewares' 10} from '../../../middlewares'
11import { getFormattedObjects } from '../../../helpers/utils' 11import { getFormattedObjects } from '../../../helpers/utils'
12import { UserVideoHistoryModel } from '../../../models/account/user-video-history' 12import { UserVideoHistoryModel } from '../../../models/user/user-video-history'
13import { sequelizeTypescript } from '../../../initializers/database' 13import { sequelizeTypescript } from '../../../initializers/database'
14import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 14import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
15 15
diff --git a/server/controllers/api/users/my-notifications.ts b/server/controllers/api/users/my-notifications.ts
index 0a9101a46..2909770da 100644
--- a/server/controllers/api/users/my-notifications.ts
+++ b/server/controllers/api/users/my-notifications.ts
@@ -1,5 +1,9 @@
1import * as express from 'express'
2import 'multer' 1import 'multer'
2import * as express from 'express'
3import { UserNotificationModel } from '@server/models/user/user-notification'
4import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
5import { UserNotificationSetting } from '../../../../shared/models/users'
6import { getFormattedObjects } from '../../../helpers/utils'
3import { 7import {
4 asyncMiddleware, 8 asyncMiddleware,
5 asyncRetryTransactionMiddleware, 9 asyncRetryTransactionMiddleware,
@@ -9,17 +13,13 @@ import {
9 setDefaultSort, 13 setDefaultSort,
10 userNotificationsSortValidator 14 userNotificationsSortValidator
11} from '../../../middlewares' 15} from '../../../middlewares'
12import { getFormattedObjects } from '../../../helpers/utils'
13import { UserNotificationModel } from '../../../models/account/user-notification'
14import { meRouter } from './me'
15import { 16import {
16 listUserNotificationsValidator, 17 listUserNotificationsValidator,
17 markAsReadUserNotificationsValidator, 18 markAsReadUserNotificationsValidator,
18 updateNotificationSettingsValidator 19 updateNotificationSettingsValidator
19} from '../../../middlewares/validators/user-notifications' 20} from '../../../middlewares/validators/user-notifications'
20import { UserNotificationSetting } from '../../../../shared/models/users' 21import { UserNotificationSettingModel } from '../../../models/user/user-notification-setting'
21import { UserNotificationSettingModel } from '../../../models/account/user-notification-setting' 22import { meRouter } from './me'
22import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
23 23
24const myNotificationsRouter = express.Router() 24const myNotificationsRouter = express.Router()
25 25
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
index 56b93276f..46a73d49e 100644
--- a/server/controllers/api/users/my-subscriptions.ts
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -27,7 +27,7 @@ import {
27 userSubscriptionsSortValidator, 27 userSubscriptionsSortValidator,
28 videosSortValidator 28 videosSortValidator
29} from '../../../middlewares/validators' 29} from '../../../middlewares/validators'
30import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 30import { ActorFollowModel } from '../../../models/actor/actor-follow'
31import { VideoModel } from '../../../models/video/video' 31import { VideoModel } from '../../../models/video/video'
32 32
33const mySubscriptionsRouter = express.Router() 33const mySubscriptionsRouter = express.Router()
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index a755d7e57..859d8b3c0 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -162,6 +162,7 @@ async function updateVideoChannelBanner (req: express.Request, res: express.Resp
162 162
163 return res.json({ banner: banner.toFormattedJSON() }) 163 return res.json({ banner: banner.toFormattedJSON() })
164} 164}
165
165async function updateVideoChannelAvatar (req: express.Request, res: express.Response) { 166async function updateVideoChannelAvatar (req: express.Request, res: express.Response) {
166 const avatarPhysicalFile = req.files['avatarfile'][0] 167 const avatarPhysicalFile = req.files['avatarfile'][0]
167 const videoChannel = res.locals.videoChannel 168 const videoChannel = res.locals.videoChannel
@@ -221,10 +222,6 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
221 222
222 try { 223 try {
223 await sequelizeTypescript.transaction(async t => { 224 await sequelizeTypescript.transaction(async t => {
224 const sequelizeOptions = {
225 transaction: t
226 }
227
228 if (videoChannelInfoToUpdate.displayName !== undefined) videoChannelInstance.name = videoChannelInfoToUpdate.displayName 225 if (videoChannelInfoToUpdate.displayName !== undefined) videoChannelInstance.name = videoChannelInfoToUpdate.displayName
229 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.description = videoChannelInfoToUpdate.description 226 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.description = videoChannelInfoToUpdate.description
230 227
@@ -238,7 +235,7 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
238 } 235 }
239 } 236 }
240 237
241 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) as MChannelBannerAccountDefault 238 const videoChannelInstanceUpdated = await videoChannelInstance.save({ transaction: t }) as MChannelBannerAccountDefault
242 await sendUpdateActor(videoChannelInstanceUpdated, t) 239 await sendUpdateActor(videoChannelInstanceUpdated, t)
243 240
244 auditLogger.update( 241 auditLogger.update(
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts
index aab16533d..b8613699b 100644
--- a/server/controllers/api/video-playlist.ts
+++ b/server/controllers/api/video-playlist.ts
@@ -202,7 +202,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
202 id: videoPlaylistCreated.id, 202 id: videoPlaylistCreated.id,
203 uuid: videoPlaylistCreated.uuid 203 uuid: videoPlaylistCreated.uuid
204 } 204 }
205 }).end() 205 })
206} 206}
207 207
208async function updateVideoPlaylist (req: express.Request, res: express.Response) { 208async function updateVideoPlaylist (req: express.Request, res: express.Response) {
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts
index f1f53d354..cfdf2773f 100644
--- a/server/controllers/api/videos/comment.ts
+++ b/server/controllers/api/videos/comment.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 2import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
3import { ResultList, ThreadsResultList, UserRight } from '../../../../shared/models' 3import { ResultList, ThreadsResultList, UserRight } from '../../../../shared/models'
4import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model' 4import { VideoCommentCreate } from '../../../../shared/models/videos/comment/video-comment.model'
5import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger' 5import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger'
6import { getFormattedObjects } from '../../../helpers/utils' 6import { getFormattedObjects } from '../../../helpers/utils'
7import { sequelizeTypescript } from '../../../initializers/database' 7import { sequelizeTypescript } from '../../../initializers/database'
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
index 3b9b887e2..ee63c7b77 100644
--- a/server/controllers/api/videos/import.ts
+++ b/server/controllers/api/videos/import.ts
@@ -3,7 +3,9 @@ import { move, readFile } from 'fs-extra'
3import * as magnetUtil from 'magnet-uri' 3import * as magnetUtil from 'magnet-uri'
4import * as parseTorrent from 'parse-torrent' 4import * as parseTorrent from 'parse-torrent'
5import { join } from 'path' 5import { join } from 'path'
6import { getEnabledResolutions } from '@server/lib/config'
6import { setVideoTags } from '@server/lib/video' 7import { setVideoTags } from '@server/lib/video'
8import { FilteredModelAttributes } from '@server/types'
7import { 9import {
8 MChannelAccountDefault, 10 MChannelAccountDefault,
9 MThumbnail, 11 MThumbnail,
@@ -14,17 +16,17 @@ import {
14 MVideoThumbnail, 16 MVideoThumbnail,
15 MVideoWithBlacklistLight 17 MVideoWithBlacklistLight
16} from '@server/types/models' 18} from '@server/types/models'
17import { MVideoImport, MVideoImportFormattable } from '@server/types/models/video/video-import' 19import { MVideoImportFormattable } from '@server/types/models/video/video-import'
18import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' 20import { ServerErrorCode, VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared'
19import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 21import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
20import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' 22import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
21import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' 23import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
22import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' 24import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
23import { isArray } from '../../../helpers/custom-validators/misc' 25import { isArray } from '../../../helpers/custom-validators/misc'
24import { createReqFiles } from '../../../helpers/express-utils' 26import { cleanUpReqFiles, createReqFiles } from '../../../helpers/express-utils'
25import { logger } from '../../../helpers/logger' 27import { logger } from '../../../helpers/logger'
26import { getSecureTorrentName } from '../../../helpers/utils' 28import { getSecureTorrentName } from '../../../helpers/utils'
27import { getYoutubeDLInfo, getYoutubeDLSubs, YoutubeDLInfo } from '../../../helpers/youtube-dl' 29import { YoutubeDL, YoutubeDLInfo } from '../../../helpers/youtube-dl'
28import { CONFIG } from '../../../initializers/config' 30import { CONFIG } from '../../../initializers/config'
29import { MIMETYPES } from '../../../initializers/constants' 31import { MIMETYPES } from '../../../initializers/constants'
30import { sequelizeTypescript } from '../../../initializers/database' 32import { sequelizeTypescript } from '../../../initializers/database'
@@ -81,22 +83,15 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
81 let magnetUri: string 83 let magnetUri: string
82 84
83 if (torrentfile) { 85 if (torrentfile) {
84 torrentName = torrentfile.originalname 86 const result = await processTorrentOrAbortRequest(req, res, torrentfile)
87 if (!result) return
85 88
86 // Rename the torrent to a secured name 89 videoName = result.name
87 const newTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, getSecureTorrentName(torrentName)) 90 torrentName = result.torrentName
88 await move(torrentfile.path, newTorrentPath)
89 torrentfile.path = newTorrentPath
90
91 const buf = await readFile(torrentfile.path)
92 const parsedTorrent = parseTorrent(buf)
93
94 videoName = isArray(parsedTorrent.name) ? parsedTorrent.name[0] : parsedTorrent.name as string
95 } else { 91 } else {
96 magnetUri = body.magnetUri 92 const result = processMagnetURI(body)
97 93 magnetUri = result.magnetUri
98 const parsed = magnetUtil.decode(magnetUri) 94 videoName = result.name
99 videoName = isArray(parsed.name) ? parsed.name[0] : parsed.name as string
100 } 95 }
101 96
102 const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName }) 97 const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName })
@@ -104,26 +99,26 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
104 const thumbnailModel = await processThumbnail(req, video) 99 const thumbnailModel = await processThumbnail(req, video)
105 const previewModel = await processPreview(req, video) 100 const previewModel = await processPreview(req, video)
106 101
107 const tags = body.tags || undefined
108 const videoImportAttributes = {
109 magnetUri,
110 torrentName,
111 state: VideoImportState.PENDING,
112 userId: user.id
113 }
114 const videoImport = await insertIntoDB({ 102 const videoImport = await insertIntoDB({
115 video, 103 video,
116 thumbnailModel, 104 thumbnailModel,
117 previewModel, 105 previewModel,
118 videoChannel: res.locals.videoChannel, 106 videoChannel: res.locals.videoChannel,
119 tags, 107 tags: body.tags || undefined,
120 videoImportAttributes, 108 user,
121 user 109 videoImportAttributes: {
110 magnetUri,
111 torrentName,
112 state: VideoImportState.PENDING,
113 userId: user.id
114 }
122 }) 115 })
123 116
124 // Create job to import the video 117 // Create job to import the video
125 const payload = { 118 const payload = {
126 type: torrentfile ? 'torrent-file' as 'torrent-file' : 'magnet-uri' as 'magnet-uri', 119 type: torrentfile
120 ? 'torrent-file' as 'torrent-file'
121 : 'magnet-uri' as 'magnet-uri',
127 videoImportId: videoImport.id, 122 videoImportId: videoImport.id,
128 magnetUri 123 magnetUri
129 } 124 }
@@ -139,10 +134,12 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
139 const targetUrl = body.targetUrl 134 const targetUrl = body.targetUrl
140 const user = res.locals.oauth.token.User 135 const user = res.locals.oauth.token.User
141 136
137 const youtubeDL = new YoutubeDL(targetUrl, getEnabledResolutions('vod'))
138
142 // Get video infos 139 // Get video infos
143 let youtubeDLInfo: YoutubeDLInfo 140 let youtubeDLInfo: YoutubeDLInfo
144 try { 141 try {
145 youtubeDLInfo = await getYoutubeDLInfo(targetUrl) 142 youtubeDLInfo = await youtubeDL.getYoutubeDLInfo()
146 } catch (err) { 143 } catch (err) {
147 logger.info('Cannot fetch information from import for URL %s.', targetUrl, { err }) 144 logger.info('Cannot fetch information from import for URL %s.', targetUrl, { err })
148 145
@@ -170,45 +167,22 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
170 previewModel = await processPreviewFromUrl(youtubeDLInfo.thumbnailUrl, video) 167 previewModel = await processPreviewFromUrl(youtubeDLInfo.thumbnailUrl, video)
171 } 168 }
172 169
173 const tags = body.tags || youtubeDLInfo.tags
174 const videoImportAttributes = {
175 targetUrl,
176 state: VideoImportState.PENDING,
177 userId: user.id
178 }
179 const videoImport = await insertIntoDB({ 170 const videoImport = await insertIntoDB({
180 video, 171 video,
181 thumbnailModel, 172 thumbnailModel,
182 previewModel, 173 previewModel,
183 videoChannel: res.locals.videoChannel, 174 videoChannel: res.locals.videoChannel,
184 tags, 175 tags: body.tags || youtubeDLInfo.tags,
185 videoImportAttributes, 176 user,
186 user 177 videoImportAttributes: {
178 targetUrl,
179 state: VideoImportState.PENDING,
180 userId: user.id
181 }
187 }) 182 })
188 183
189 // Get video subtitles 184 // Get video subtitles
190 try { 185 await processYoutubeSubtitles(youtubeDL, targetUrl, video.id)
191 const subtitles = await getYoutubeDLSubs(targetUrl)
192
193 logger.info('Will create %s subtitles from youtube import %s.', subtitles.length, targetUrl)
194
195 for (const subtitle of subtitles) {
196 const videoCaption = new VideoCaptionModel({
197 videoId: video.id,
198 language: subtitle.language,
199 filename: VideoCaptionModel.generateCaptionName(subtitle.language)
200 }) as MVideoCaption
201
202 // Move physical file
203 await moveAndProcessCaptionFile(subtitle, videoCaption)
204
205 await sequelizeTypescript.transaction(async t => {
206 await VideoCaptionModel.insertOrReplaceLanguage(videoCaption, t)
207 })
208 }
209 } catch (err) {
210 logger.warn('Cannot get video subtitles.', { err })
211 }
212 186
213 // Create job to import the video 187 // Create job to import the video
214 const payload = { 188 const payload = {
@@ -240,7 +214,9 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You
240 privacy: body.privacy || VideoPrivacy.PRIVATE, 214 privacy: body.privacy || VideoPrivacy.PRIVATE,
241 duration: 0, // duration will be set by the import job 215 duration: 0, // duration will be set by the import job
242 channelId: channelId, 216 channelId: channelId,
243 originallyPublishedAt: body.originallyPublishedAt || importData.originallyPublishedAt 217 originallyPublishedAt: body.originallyPublishedAt
218 ? new Date(body.originallyPublishedAt)
219 : importData.originallyPublishedAt
244 } 220 }
245 const video = new VideoModel(videoData) 221 const video = new VideoModel(videoData)
246 video.url = getLocalVideoActivityPubUrl(video) 222 video.url = getLocalVideoActivityPubUrl(video)
@@ -304,7 +280,7 @@ async function insertIntoDB (parameters: {
304 previewModel: MThumbnail 280 previewModel: MThumbnail
305 videoChannel: MChannelAccountDefault 281 videoChannel: MChannelAccountDefault
306 tags: string[] 282 tags: string[]
307 videoImportAttributes: Partial<MVideoImport> 283 videoImportAttributes: FilteredModelAttributes<VideoImportModel>
308 user: MUser 284 user: MUser
309}): Promise<MVideoImportFormattable> { 285}): Promise<MVideoImportFormattable> {
310 const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters 286 const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters
@@ -342,3 +318,71 @@ async function insertIntoDB (parameters: {
342 318
343 return videoImport 319 return videoImport
344} 320}
321
322async function processTorrentOrAbortRequest (req: express.Request, res: express.Response, torrentfile: Express.Multer.File) {
323 const torrentName = torrentfile.originalname
324
325 // Rename the torrent to a secured name
326 const newTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, getSecureTorrentName(torrentName))
327 await move(torrentfile.path, newTorrentPath, { overwrite: true })
328 torrentfile.path = newTorrentPath
329
330 const buf = await readFile(torrentfile.path)
331 const parsedTorrent = parseTorrent(buf) as parseTorrent.Instance
332
333 if (parsedTorrent.files.length !== 1) {
334 cleanUpReqFiles(req)
335
336 res.status(HttpStatusCode.BAD_REQUEST_400)
337 .json({
338 code: ServerErrorCode.INCORRECT_FILES_IN_TORRENT,
339 error: 'Torrents with only 1 file are supported.'
340 })
341
342 return undefined
343 }
344
345 return {
346 name: extractNameFromArray(parsedTorrent.name),
347 torrentName
348 }
349}
350
351function processMagnetURI (body: VideoImportCreate) {
352 const magnetUri = body.magnetUri
353 const parsed = magnetUtil.decode(magnetUri)
354
355 return {
356 name: extractNameFromArray(parsed.name),
357 magnetUri
358 }
359}
360
361function extractNameFromArray (name: string | string[]) {
362 return isArray(name) ? name[0] : name
363}
364
365async function processYoutubeSubtitles (youtubeDL: YoutubeDL, targetUrl: string, videoId: number) {
366 try {
367 const subtitles = await youtubeDL.getYoutubeDLSubs()
368
369 logger.info('Will create %s subtitles from youtube import %s.', subtitles.length, targetUrl)
370
371 for (const subtitle of subtitles) {
372 const videoCaption = new VideoCaptionModel({
373 videoId,
374 language: subtitle.language,
375 filename: VideoCaptionModel.generateCaptionName(subtitle.language)
376 }) as MVideoCaption
377
378 // Move physical file
379 await moveAndProcessCaptionFile(subtitle, videoCaption)
380
381 await sequelizeTypescript.transaction(async t => {
382 await VideoCaptionModel.insertOrReplaceLanguage(videoCaption, t)
383 })
384 }
385 } catch (err) {
386 logger.warn('Cannot get video subtitles.', { err })
387 }
388}
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index c32626d30..6483d2e8a 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -1,43 +1,20 @@
1import * as express from 'express' 1import * as express from 'express'
2import { move } from 'fs-extra'
3import { extname } from 'path'
4import toInt from 'validator/lib/toInt' 2import toInt from 'validator/lib/toInt'
5import { deleteResumableUploadMetaFile, getResumableUploadPath } from '@server/helpers/upload'
6import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
7import { changeVideoChannelShare } from '@server/lib/activitypub/share'
8import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url'
9import { LiveManager } from '@server/lib/live-manager' 3import { LiveManager } from '@server/lib/live-manager'
10import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
11import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
12import { getServerActor } from '@server/models/application/application' 4import { getServerActor } from '@server/models/application/application'
13import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' 5import { VideosCommonQuery } from '../../../../shared'
14import { uploadx } from '@uploadx/core'
15import { VideoCreate, VideosCommonQuery, VideoState, VideoUpdate } from '../../../../shared'
16import { HttpStatusCode } from '../../../../shared/core-utils/miscs' 6import { HttpStatusCode } from '../../../../shared/core-utils/miscs'
17import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 7import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
18import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' 8import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
19import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils' 9import { logger } from '../../../helpers/logger'
20import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils'
21import { logger, loggerTagsFactory } from '../../../helpers/logger'
22import { getFormattedObjects } from '../../../helpers/utils' 10import { getFormattedObjects } from '../../../helpers/utils'
23import { CONFIG } from '../../../initializers/config' 11import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers/constants'
24import {
25 DEFAULT_AUDIO_RESOLUTION,
26 MIMETYPES,
27 VIDEO_CATEGORIES,
28 VIDEO_LANGUAGES,
29 VIDEO_LICENCES,
30 VIDEO_PRIVACIES
31} from '../../../initializers/constants'
32import { sequelizeTypescript } from '../../../initializers/database' 12import { sequelizeTypescript } from '../../../initializers/database'
33import { sendView } from '../../../lib/activitypub/send/send-view' 13import { sendView } from '../../../lib/activitypub/send/send-view'
34import { federateVideoIfNeeded, fetchRemoteVideoDescription } from '../../../lib/activitypub/videos' 14import { fetchRemoteVideoDescription } from '../../../lib/activitypub/videos'
35import { JobQueue } from '../../../lib/job-queue' 15import { JobQueue } from '../../../lib/job-queue'
36import { Notifier } from '../../../lib/notifier'
37import { Hooks } from '../../../lib/plugins/hooks' 16import { Hooks } from '../../../lib/plugins/hooks'
38import { Redis } from '../../../lib/redis' 17import { Redis } from '../../../lib/redis'
39import { generateVideoMiniature } from '../../../lib/thumbnail'
40import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
41import { 18import {
42 asyncMiddleware, 19 asyncMiddleware,
43 asyncRetryTransactionMiddleware, 20 asyncRetryTransactionMiddleware,
@@ -49,16 +26,11 @@ import {
49 setDefaultPagination, 26 setDefaultPagination,
50 setDefaultVideosSort, 27 setDefaultVideosSort,
51 videoFileMetadataGetValidator, 28 videoFileMetadataGetValidator,
52 videosAddLegacyValidator,
53 videosAddResumableInitValidator,
54 videosAddResumableValidator,
55 videosCustomGetValidator, 29 videosCustomGetValidator,
56 videosGetValidator, 30 videosGetValidator,
57 videosRemoveValidator, 31 videosRemoveValidator,
58 videosSortValidator, 32 videosSortValidator
59 videosUpdateValidator
60} from '../../../middlewares' 33} from '../../../middlewares'
61import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
62import { VideoModel } from '../../../models/video/video' 34import { VideoModel } from '../../../models/video/video'
63import { VideoFileModel } from '../../../models/video/video-file' 35import { VideoFileModel } from '../../../models/video/video-file'
64import { blacklistRouter } from './blacklist' 36import { blacklistRouter } from './blacklist'
@@ -68,40 +40,12 @@ import { videoImportsRouter } from './import'
68import { liveRouter } from './live' 40import { liveRouter } from './live'
69import { ownershipVideoRouter } from './ownership' 41import { ownershipVideoRouter } from './ownership'
70import { rateVideoRouter } from './rate' 42import { rateVideoRouter } from './rate'
43import { updateRouter } from './update'
44import { uploadRouter } from './upload'
71import { watchingRouter } from './watching' 45import { watchingRouter } from './watching'
72 46
73const lTags = loggerTagsFactory('api', 'video')
74const auditLogger = auditLoggerFactory('videos') 47const auditLogger = auditLoggerFactory('videos')
75const videosRouter = express.Router() 48const videosRouter = express.Router()
76const uploadxMiddleware = uploadx.upload({ directory: getResumableUploadPath() })
77
78const reqVideoFileAdd = createReqFiles(
79 [ 'videofile', 'thumbnailfile', 'previewfile' ],
80 Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT),
81 {
82 videofile: CONFIG.STORAGE.TMP_DIR,
83 thumbnailfile: CONFIG.STORAGE.TMP_DIR,
84 previewfile: CONFIG.STORAGE.TMP_DIR
85 }
86)
87
88const reqVideoFileAddResumable = createReqFiles(
89 [ 'thumbnailfile', 'previewfile' ],
90 MIMETYPES.IMAGE.MIMETYPE_EXT,
91 {
92 thumbnailfile: getResumableUploadPath(),
93 previewfile: getResumableUploadPath()
94 }
95)
96
97const reqVideoFileUpdate = createReqFiles(
98 [ 'thumbnailfile', 'previewfile' ],
99 MIMETYPES.IMAGE.MIMETYPE_EXT,
100 {
101 thumbnailfile: CONFIG.STORAGE.TMP_DIR,
102 previewfile: CONFIG.STORAGE.TMP_DIR
103 }
104)
105 49
106videosRouter.use('/', blacklistRouter) 50videosRouter.use('/', blacklistRouter)
107videosRouter.use('/', rateVideoRouter) 51videosRouter.use('/', rateVideoRouter)
@@ -111,6 +55,8 @@ videosRouter.use('/', videoImportsRouter)
111videosRouter.use('/', ownershipVideoRouter) 55videosRouter.use('/', ownershipVideoRouter)
112videosRouter.use('/', watchingRouter) 56videosRouter.use('/', watchingRouter)
113videosRouter.use('/', liveRouter) 57videosRouter.use('/', liveRouter)
58videosRouter.use('/', uploadRouter)
59videosRouter.use('/', updateRouter)
114 60
115videosRouter.get('/categories', listVideoCategories) 61videosRouter.get('/categories', listVideoCategories)
116videosRouter.get('/licences', listVideoLicences) 62videosRouter.get('/licences', listVideoLicences)
@@ -127,39 +73,6 @@ videosRouter.get('/',
127 asyncMiddleware(listVideos) 73 asyncMiddleware(listVideos)
128) 74)
129 75
130videosRouter.post('/upload',
131 authenticate,
132 reqVideoFileAdd,
133 asyncMiddleware(videosAddLegacyValidator),
134 asyncRetryTransactionMiddleware(addVideoLegacy)
135)
136
137videosRouter.post('/upload-resumable',
138 authenticate,
139 reqVideoFileAddResumable,
140 asyncMiddleware(videosAddResumableInitValidator),
141 uploadxMiddleware
142)
143
144videosRouter.delete('/upload-resumable',
145 authenticate,
146 uploadxMiddleware
147)
148
149videosRouter.put('/upload-resumable',
150 authenticate,
151 uploadxMiddleware, // uploadx doesn't use call next() before the file upload completes
152 asyncMiddleware(videosAddResumableValidator),
153 asyncMiddleware(addVideoResumable)
154)
155
156videosRouter.put('/:id',
157 authenticate,
158 reqVideoFileUpdate,
159 asyncMiddleware(videosUpdateValidator),
160 asyncRetryTransactionMiddleware(updateVideo)
161)
162
163videosRouter.get('/:id/description', 76videosRouter.get('/:id/description',
164 asyncMiddleware(videosGetValidator), 77 asyncMiddleware(videosGetValidator),
165 asyncMiddleware(getVideoDescription) 78 asyncMiddleware(getVideoDescription)
@@ -209,279 +122,7 @@ function listVideoPrivacies (_req: express.Request, res: express.Response) {
209 res.json(VIDEO_PRIVACIES) 122 res.json(VIDEO_PRIVACIES)
210} 123}
211 124
212async function addVideoLegacy (req: express.Request, res: express.Response) { 125async function getVideo (_req: express.Request, res: express.Response) {
213 // Uploading the video could be long
214 // Set timeout to 10 minutes, as Express's default is 2 minutes
215 req.setTimeout(1000 * 60 * 10, () => {
216 logger.error('Upload video has timed out.')
217 return res.sendStatus(HttpStatusCode.REQUEST_TIMEOUT_408)
218 })
219
220 const videoPhysicalFile = req.files['videofile'][0]
221 const videoInfo: VideoCreate = req.body
222 const files = req.files
223
224 return addVideo({ res, videoPhysicalFile, videoInfo, files })
225}
226
227async function addVideoResumable (_req: express.Request, res: express.Response) {
228 const videoPhysicalFile = res.locals.videoFileResumable
229 const videoInfo = videoPhysicalFile.metadata
230 const files = { previewfile: videoInfo.previewfile }
231
232 // Don't need the meta file anymore
233 await deleteResumableUploadMetaFile(videoPhysicalFile.path)
234
235 return addVideo({ res, videoPhysicalFile, videoInfo, files })
236}
237
238async function addVideo (options: {
239 res: express.Response
240 videoPhysicalFile: express.VideoUploadFile
241 videoInfo: VideoCreate
242 files: express.UploadFiles
243}) {
244 const { res, videoPhysicalFile, videoInfo, files } = options
245 const videoChannel = res.locals.videoChannel
246 const user = res.locals.oauth.token.User
247
248 const videoData = buildLocalVideoFromReq(videoInfo, videoChannel.id)
249
250 videoData.state = CONFIG.TRANSCODING.ENABLED
251 ? VideoState.TO_TRANSCODE
252 : VideoState.PUBLISHED
253
254 videoData.duration = videoPhysicalFile.duration // duration was added by a previous middleware
255
256 const video = new VideoModel(videoData) as MVideoFullLight
257 video.VideoChannel = videoChannel
258 video.url = getLocalVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
259
260 const videoFile = new VideoFileModel({
261 extname: extname(videoPhysicalFile.filename),
262 size: videoPhysicalFile.size,
263 videoStreamingPlaylistId: null,
264 metadata: await getMetadataFromFile(videoPhysicalFile.path)
265 })
266
267 if (videoFile.isAudio()) {
268 videoFile.resolution = DEFAULT_AUDIO_RESOLUTION
269 } else {
270 videoFile.fps = await getVideoFileFPS(videoPhysicalFile.path)
271 videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).videoFileResolution
272 }
273
274 videoFile.filename = generateVideoFilename(video, false, videoFile.resolution, videoFile.extname)
275
276 // Move physical file
277 const destination = getVideoFilePath(video, videoFile)
278 await move(videoPhysicalFile.path, destination)
279 // This is important in case if there is another attempt in the retry process
280 videoPhysicalFile.filename = getVideoFilePath(video, videoFile)
281 videoPhysicalFile.path = destination
282
283 const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({
284 video,
285 files,
286 fallback: type => generateVideoMiniature({ video, videoFile, type })
287 })
288
289 const { videoCreated } = await sequelizeTypescript.transaction(async t => {
290 const sequelizeOptions = { transaction: t }
291
292 const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
293
294 await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
295 await videoCreated.addAndSaveThumbnail(previewModel, t)
296
297 // Do not forget to add video channel information to the created video
298 videoCreated.VideoChannel = res.locals.videoChannel
299
300 videoFile.videoId = video.id
301 await videoFile.save(sequelizeOptions)
302
303 video.VideoFiles = [ videoFile ]
304
305 await setVideoTags({ video, tags: videoInfo.tags, transaction: t })
306
307 // Schedule an update in the future?
308 if (videoInfo.scheduleUpdate) {
309 await ScheduleVideoUpdateModel.create({
310 videoId: video.id,
311 updateAt: videoInfo.scheduleUpdate.updateAt,
312 privacy: videoInfo.scheduleUpdate.privacy || null
313 }, { transaction: t })
314 }
315
316 // Channel has a new content, set as updated
317 await videoCreated.VideoChannel.setAsUpdated(t)
318
319 await autoBlacklistVideoIfNeeded({
320 video,
321 user,
322 isRemote: false,
323 isNew: true,
324 transaction: t
325 })
326
327 auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
328 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid, lTags(videoCreated.uuid))
329
330 return { videoCreated }
331 })
332
333 // Create the torrent file in async way because it could be long
334 createTorrentAndSetInfoHashAsync(video, videoFile)
335 .catch(err => logger.error('Cannot create torrent file for video %s', video.url, { err, ...lTags(video.uuid) }))
336 .then(() => VideoModel.loadAndPopulateAccountAndServerAndTags(video.id))
337 .then(refreshedVideo => {
338 if (!refreshedVideo) return
339
340 // Only federate and notify after the torrent creation
341 Notifier.Instance.notifyOnNewVideoIfNeeded(refreshedVideo)
342
343 return retryTransactionWrapper(() => {
344 return sequelizeTypescript.transaction(t => federateVideoIfNeeded(refreshedVideo, true, t))
345 })
346 })
347 .catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err, ...lTags(video.uuid) }))
348
349 if (video.state === VideoState.TO_TRANSCODE) {
350 await addOptimizeOrMergeAudioJob(videoCreated, videoFile, user)
351 }
352
353 Hooks.runAction('action:api.video.uploaded', { video: videoCreated })
354
355 return res.json({
356 video: {
357 id: videoCreated.id,
358 uuid: videoCreated.uuid
359 }
360 })
361}
362
363async function updateVideo (req: express.Request, res: express.Response) {
364 const videoInstance = res.locals.videoAll
365 const videoFieldsSave = videoInstance.toJSON()
366 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
367 const videoInfoToUpdate: VideoUpdate = req.body
368
369 const wasConfidentialVideo = videoInstance.isConfidential()
370 const hadPrivacyForFederation = videoInstance.hasPrivacyForFederation()
371
372 const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({
373 video: videoInstance,
374 files: req.files,
375 fallback: () => Promise.resolve(undefined),
376 automaticallyGenerated: false
377 })
378
379 try {
380 const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => {
381 const sequelizeOptions = { transaction: t }
382 const oldVideoChannel = videoInstance.VideoChannel
383
384 if (videoInfoToUpdate.name !== undefined) videoInstance.name = videoInfoToUpdate.name
385 if (videoInfoToUpdate.category !== undefined) videoInstance.category = videoInfoToUpdate.category
386 if (videoInfoToUpdate.licence !== undefined) videoInstance.licence = videoInfoToUpdate.licence
387 if (videoInfoToUpdate.language !== undefined) videoInstance.language = videoInfoToUpdate.language
388 if (videoInfoToUpdate.nsfw !== undefined) videoInstance.nsfw = videoInfoToUpdate.nsfw
389 if (videoInfoToUpdate.waitTranscoding !== undefined) videoInstance.waitTranscoding = videoInfoToUpdate.waitTranscoding
390 if (videoInfoToUpdate.support !== undefined) videoInstance.support = videoInfoToUpdate.support
391 if (videoInfoToUpdate.description !== undefined) videoInstance.description = videoInfoToUpdate.description
392 if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.commentsEnabled = videoInfoToUpdate.commentsEnabled
393 if (videoInfoToUpdate.downloadEnabled !== undefined) videoInstance.downloadEnabled = videoInfoToUpdate.downloadEnabled
394
395 if (videoInfoToUpdate.originallyPublishedAt !== undefined && videoInfoToUpdate.originallyPublishedAt !== null) {
396 videoInstance.originallyPublishedAt = new Date(videoInfoToUpdate.originallyPublishedAt)
397 }
398
399 let isNewVideo = false
400 if (videoInfoToUpdate.privacy !== undefined) {
401 isNewVideo = videoInstance.isNewVideo(videoInfoToUpdate.privacy)
402
403 const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10)
404 videoInstance.setPrivacy(newPrivacy)
405
406 // Unfederate the video if the new privacy is not compatible with federation
407 if (hadPrivacyForFederation && !videoInstance.hasPrivacyForFederation()) {
408 await VideoModel.sendDelete(videoInstance, { transaction: t })
409 }
410 }
411
412 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) as MVideoFullLight
413
414 if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t)
415 if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t)
416
417 // Video tags update?
418 if (videoInfoToUpdate.tags !== undefined) {
419 await setVideoTags({
420 video: videoInstanceUpdated,
421 tags: videoInfoToUpdate.tags,
422 transaction: t
423 })
424 }
425
426 // Video channel update?
427 if (res.locals.videoChannel && videoInstanceUpdated.channelId !== res.locals.videoChannel.id) {
428 await videoInstanceUpdated.$set('VideoChannel', res.locals.videoChannel, { transaction: t })
429 videoInstanceUpdated.VideoChannel = res.locals.videoChannel
430
431 if (hadPrivacyForFederation === true) await changeVideoChannelShare(videoInstanceUpdated, oldVideoChannel, t)
432 }
433
434 // Schedule an update in the future?
435 if (videoInfoToUpdate.scheduleUpdate) {
436 await ScheduleVideoUpdateModel.upsert({
437 videoId: videoInstanceUpdated.id,
438 updateAt: videoInfoToUpdate.scheduleUpdate.updateAt,
439 privacy: videoInfoToUpdate.scheduleUpdate.privacy || null
440 }, { transaction: t })
441 } else if (videoInfoToUpdate.scheduleUpdate === null) {
442 await ScheduleVideoUpdateModel.deleteByVideoId(videoInstanceUpdated.id, t)
443 }
444
445 await autoBlacklistVideoIfNeeded({
446 video: videoInstanceUpdated,
447 user: res.locals.oauth.token.User,
448 isRemote: false,
449 isNew: false,
450 transaction: t
451 })
452
453 await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t)
454
455 auditLogger.update(
456 getAuditIdFromRes(res),
457 new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()),
458 oldVideoAuditView
459 )
460 logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid, lTags(videoInstance.uuid))
461
462 return videoInstanceUpdated
463 })
464
465 if (wasConfidentialVideo) {
466 Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated)
467 }
468
469 Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body })
470 } catch (err) {
471 // Force fields we want to update
472 // If the transaction is retried, sequelize will think the object has not changed
473 // So it will skip the SQL request, even if the last one was ROLLBACKed!
474 resetSequelizeInstance(videoInstance, videoFieldsSave)
475
476 throw err
477 }
478
479 return res.type('json')
480 .status(HttpStatusCode.NO_CONTENT_204)
481 .end()
482}
483
484async function getVideo (req: express.Request, res: express.Response) {
485 // We need more attributes 126 // We need more attributes
486 const userId: number = res.locals.oauth ? res.locals.oauth.token.User.id : null 127 const userId: number = res.locals.oauth ? res.locals.oauth.token.User.id : null
487 128
@@ -543,13 +184,10 @@ async function viewVideo (req: express.Request, res: express.Response) {
543 184
544async function getVideoDescription (req: express.Request, res: express.Response) { 185async function getVideoDescription (req: express.Request, res: express.Response) {
545 const videoInstance = res.locals.videoAll 186 const videoInstance = res.locals.videoAll
546 let description = ''
547 187
548 if (videoInstance.isOwned()) { 188 const description = videoInstance.isOwned()
549 description = videoInstance.description 189 ? videoInstance.description
550 } else { 190 : await fetchRemoteVideoDescription(videoInstance)
551 description = await fetchRemoteVideoDescription(videoInstance)
552 }
553 191
554 return res.json({ description }) 192 return res.json({ description })
555} 193}
@@ -591,7 +229,7 @@ async function listVideos (req: express.Request, res: express.Response) {
591 return res.json(getFormattedObjects(resultList.data, resultList.total)) 229 return res.json(getFormattedObjects(resultList.data, resultList.total))
592} 230}
593 231
594async function removeVideo (req: express.Request, res: express.Response) { 232async function removeVideo (_req: express.Request, res: express.Response) {
595 const videoInstance = res.locals.videoAll 233 const videoInstance = res.locals.videoAll
596 234
597 await sequelizeTypescript.transaction(async t => { 235 await sequelizeTypescript.transaction(async t => {
@@ -607,17 +245,3 @@ async function removeVideo (req: express.Request, res: express.Response) {
607 .status(HttpStatusCode.NO_CONTENT_204) 245 .status(HttpStatusCode.NO_CONTENT_204)
608 .end() 246 .end()
609} 247}
610
611async function createTorrentAndSetInfoHashAsync (video: MVideo, fileArg: MVideoFile) {
612 await createTorrentAndSetInfoHash(video, fileArg)
613
614 // Refresh videoFile because the createTorrentAndSetInfoHash could be long
615 const refreshedFile = await VideoFileModel.loadWithVideo(fileArg.id)
616 // File does not exist anymore, remove the generated torrent
617 if (!refreshedFile) return fileArg.removeTorrent()
618
619 refreshedFile.infoHash = fileArg.infoHash
620 refreshedFile.torrentFilename = fileArg.torrentFilename
621
622 return refreshedFile.save()
623}
diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts
index a85d7c30b..6102f28dc 100644
--- a/server/controllers/api/videos/ownership.ts
+++ b/server/controllers/api/videos/ownership.ts
@@ -99,7 +99,7 @@ async function listVideoOwnership (req: express.Request, res: express.Response)
99 return res.json(getFormattedObjects(resultList.data, resultList.total)) 99 return res.json(getFormattedObjects(resultList.data, resultList.total))
100} 100}
101 101
102async function acceptOwnership (req: express.Request, res: express.Response) { 102function acceptOwnership (req: express.Request, res: express.Response) {
103 return sequelizeTypescript.transaction(async t => { 103 return sequelizeTypescript.transaction(async t => {
104 const videoChangeOwnership = res.locals.videoChangeOwnership 104 const videoChangeOwnership = res.locals.videoChangeOwnership
105 const channel = res.locals.videoChannel 105 const channel = res.locals.videoChannel
@@ -126,7 +126,7 @@ async function acceptOwnership (req: express.Request, res: express.Response) {
126 }) 126 })
127} 127}
128 128
129async function refuseOwnership (req: express.Request, res: express.Response) { 129function refuseOwnership (req: express.Request, res: express.Response) {
130 return sequelizeTypescript.transaction(async t => { 130 return sequelizeTypescript.transaction(async t => {
131 const videoChangeOwnership = res.locals.videoChangeOwnership 131 const videoChangeOwnership = res.locals.videoChangeOwnership
132 132
diff --git a/server/controllers/api/videos/update.ts b/server/controllers/api/videos/update.ts
new file mode 100644
index 000000000..2450abd0e
--- /dev/null
+++ b/server/controllers/api/videos/update.ts
@@ -0,0 +1,191 @@
1import * as express from 'express'
2import { Transaction } from 'sequelize/types'
3import { changeVideoChannelShare } from '@server/lib/activitypub/share'
4import { buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
5import { FilteredModelAttributes } from '@server/types'
6import { MVideoFullLight } from '@server/types/models'
7import { VideoUpdate } from '../../../../shared'
8import { HttpStatusCode } from '../../../../shared/core-utils/miscs'
9import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
10import { resetSequelizeInstance } from '../../../helpers/database-utils'
11import { createReqFiles } from '../../../helpers/express-utils'
12import { logger, loggerTagsFactory } from '../../../helpers/logger'
13import { CONFIG } from '../../../initializers/config'
14import { MIMETYPES } from '../../../initializers/constants'
15import { sequelizeTypescript } from '../../../initializers/database'
16import { federateVideoIfNeeded } from '../../../lib/activitypub/videos'
17import { Notifier } from '../../../lib/notifier'
18import { Hooks } from '../../../lib/plugins/hooks'
19import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
20import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares'
21import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
22import { VideoModel } from '../../../models/video/video'
23
24const lTags = loggerTagsFactory('api', 'video')
25const auditLogger = auditLoggerFactory('videos')
26const updateRouter = express.Router()
27
28const reqVideoFileUpdate = createReqFiles(
29 [ 'thumbnailfile', 'previewfile' ],
30 MIMETYPES.IMAGE.MIMETYPE_EXT,
31 {
32 thumbnailfile: CONFIG.STORAGE.TMP_DIR,
33 previewfile: CONFIG.STORAGE.TMP_DIR
34 }
35)
36
37updateRouter.put('/:id',
38 authenticate,
39 reqVideoFileUpdate,
40 asyncMiddleware(videosUpdateValidator),
41 asyncRetryTransactionMiddleware(updateVideo)
42)
43
44// ---------------------------------------------------------------------------
45
46export {
47 updateRouter
48}
49
50// ---------------------------------------------------------------------------
51
52export async function updateVideo (req: express.Request, res: express.Response) {
53 const videoInstance = res.locals.videoAll
54 const videoFieldsSave = videoInstance.toJSON()
55 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
56 const videoInfoToUpdate: VideoUpdate = req.body
57
58 const wasConfidentialVideo = videoInstance.isConfidential()
59 const hadPrivacyForFederation = videoInstance.hasPrivacyForFederation()
60
61 const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({
62 video: videoInstance,
63 files: req.files,
64 fallback: () => Promise.resolve(undefined),
65 automaticallyGenerated: false
66 })
67
68 try {
69 const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => {
70 const sequelizeOptions = { transaction: t }
71 const oldVideoChannel = videoInstance.VideoChannel
72
73 const keysToUpdate: (keyof VideoUpdate & FilteredModelAttributes<VideoModel>)[] = [
74 'name',
75 'category',
76 'licence',
77 'language',
78 'nsfw',
79 'waitTranscoding',
80 'support',
81 'description',
82 'commentsEnabled',
83 'downloadEnabled'
84 ]
85
86 for (const key of keysToUpdate) {
87 if (videoInfoToUpdate[key] !== undefined) videoInstance.set(key, videoInfoToUpdate[key])
88 }
89
90 if (videoInfoToUpdate.originallyPublishedAt !== undefined && videoInfoToUpdate.originallyPublishedAt !== null) {
91 videoInstance.originallyPublishedAt = new Date(videoInfoToUpdate.originallyPublishedAt)
92 }
93
94 // Privacy update?
95 let isNewVideo = false
96 if (videoInfoToUpdate.privacy !== undefined) {
97 isNewVideo = await updateVideoPrivacy({ videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction: t })
98 }
99
100 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) as MVideoFullLight
101
102 // Thumbnail & preview updates?
103 if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t)
104 if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t)
105
106 // Video tags update?
107 if (videoInfoToUpdate.tags !== undefined) {
108 await setVideoTags({ video: videoInstanceUpdated, tags: videoInfoToUpdate.tags, transaction: t })
109 }
110
111 // Video channel update?
112 if (res.locals.videoChannel && videoInstanceUpdated.channelId !== res.locals.videoChannel.id) {
113 await videoInstanceUpdated.$set('VideoChannel', res.locals.videoChannel, { transaction: t })
114 videoInstanceUpdated.VideoChannel = res.locals.videoChannel
115
116 if (hadPrivacyForFederation === true) await changeVideoChannelShare(videoInstanceUpdated, oldVideoChannel, t)
117 }
118
119 // Schedule an update in the future?
120 await updateSchedule(videoInstanceUpdated, videoInfoToUpdate, t)
121
122 await autoBlacklistVideoIfNeeded({
123 video: videoInstanceUpdated,
124 user: res.locals.oauth.token.User,
125 isRemote: false,
126 isNew: false,
127 transaction: t
128 })
129
130 await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t)
131
132 auditLogger.update(
133 getAuditIdFromRes(res),
134 new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()),
135 oldVideoAuditView
136 )
137 logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid, lTags(videoInstance.uuid))
138
139 return videoInstanceUpdated
140 })
141
142 if (wasConfidentialVideo) {
143 Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated)
144 }
145
146 Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body })
147 } catch (err) {
148 // Force fields we want to update
149 // If the transaction is retried, sequelize will think the object has not changed
150 // So it will skip the SQL request, even if the last one was ROLLBACKed!
151 resetSequelizeInstance(videoInstance, videoFieldsSave)
152
153 throw err
154 }
155
156 return res.type('json')
157 .status(HttpStatusCode.NO_CONTENT_204)
158 .end()
159}
160
161async function updateVideoPrivacy (options: {
162 videoInstance: MVideoFullLight
163 videoInfoToUpdate: VideoUpdate
164 hadPrivacyForFederation: boolean
165 transaction: Transaction
166}) {
167 const { videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction } = options
168 const isNewVideo = videoInstance.isNewVideo(videoInfoToUpdate.privacy)
169
170 const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10)
171 videoInstance.setPrivacy(newPrivacy)
172
173 // Unfederate the video if the new privacy is not compatible with federation
174 if (hadPrivacyForFederation && !videoInstance.hasPrivacyForFederation()) {
175 await VideoModel.sendDelete(videoInstance, { transaction })
176 }
177
178 return isNewVideo
179}
180
181function updateSchedule (videoInstance: MVideoFullLight, videoInfoToUpdate: VideoUpdate, transaction: Transaction) {
182 if (videoInfoToUpdate.scheduleUpdate) {
183 return ScheduleVideoUpdateModel.upsert({
184 videoId: videoInstance.id,
185 updateAt: new Date(videoInfoToUpdate.scheduleUpdate.updateAt),
186 privacy: videoInfoToUpdate.scheduleUpdate.privacy || null
187 }, { transaction })
188 } else if (videoInfoToUpdate.scheduleUpdate === null) {
189 return ScheduleVideoUpdateModel.deleteByVideoId(videoInstance.id, transaction)
190 }
191}
diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts
new file mode 100644
index 000000000..ebc17c760
--- /dev/null
+++ b/server/controllers/api/videos/upload.ts
@@ -0,0 +1,269 @@
1import * as express from 'express'
2import { move } from 'fs-extra'
3import { extname } from 'path'
4import { deleteResumableUploadMetaFile, getResumableUploadPath } from '@server/helpers/upload'
5import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
6import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url'
7import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
8import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
9import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
10import { uploadx } from '@uploadx/core'
11import { VideoCreate, VideoState } from '../../../../shared'
12import { HttpStatusCode } from '../../../../shared/core-utils/miscs'
13import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
14import { retryTransactionWrapper } from '../../../helpers/database-utils'
15import { createReqFiles } from '../../../helpers/express-utils'
16import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils'
17import { logger, loggerTagsFactory } from '../../../helpers/logger'
18import { CONFIG } from '../../../initializers/config'
19import { DEFAULT_AUDIO_RESOLUTION, MIMETYPES } from '../../../initializers/constants'
20import { sequelizeTypescript } from '../../../initializers/database'
21import { federateVideoIfNeeded } from '../../../lib/activitypub/videos'
22import { Notifier } from '../../../lib/notifier'
23import { Hooks } from '../../../lib/plugins/hooks'
24import { generateVideoMiniature } from '../../../lib/thumbnail'
25import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
26import {
27 asyncMiddleware,
28 asyncRetryTransactionMiddleware,
29 authenticate,
30 videosAddLegacyValidator,
31 videosAddResumableInitValidator,
32 videosAddResumableValidator
33} from '../../../middlewares'
34import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
35import { VideoModel } from '../../../models/video/video'
36import { VideoFileModel } from '../../../models/video/video-file'
37
38const lTags = loggerTagsFactory('api', 'video')
39const auditLogger = auditLoggerFactory('videos')
40const uploadRouter = express.Router()
41const uploadxMiddleware = uploadx.upload({ directory: getResumableUploadPath() })
42
43const reqVideoFileAdd = createReqFiles(
44 [ 'videofile', 'thumbnailfile', 'previewfile' ],
45 Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT),
46 {
47 videofile: CONFIG.STORAGE.TMP_DIR,
48 thumbnailfile: CONFIG.STORAGE.TMP_DIR,
49 previewfile: CONFIG.STORAGE.TMP_DIR
50 }
51)
52
53const reqVideoFileAddResumable = createReqFiles(
54 [ 'thumbnailfile', 'previewfile' ],
55 MIMETYPES.IMAGE.MIMETYPE_EXT,
56 {
57 thumbnailfile: getResumableUploadPath(),
58 previewfile: getResumableUploadPath()
59 }
60)
61
62uploadRouter.post('/upload',
63 authenticate,
64 reqVideoFileAdd,
65 asyncMiddleware(videosAddLegacyValidator),
66 asyncRetryTransactionMiddleware(addVideoLegacy)
67)
68
69uploadRouter.post('/upload-resumable',
70 authenticate,
71 reqVideoFileAddResumable,
72 asyncMiddleware(videosAddResumableInitValidator),
73 uploadxMiddleware
74)
75
76uploadRouter.delete('/upload-resumable',
77 authenticate,
78 uploadxMiddleware
79)
80
81uploadRouter.put('/upload-resumable',
82 authenticate,
83 uploadxMiddleware, // uploadx doesn't use call next() before the file upload completes
84 asyncMiddleware(videosAddResumableValidator),
85 asyncMiddleware(addVideoResumable)
86)
87
88// ---------------------------------------------------------------------------
89
90export {
91 uploadRouter
92}
93
94// ---------------------------------------------------------------------------
95
96export async function addVideoLegacy (req: express.Request, res: express.Response) {
97 // Uploading the video could be long
98 // Set timeout to 10 minutes, as Express's default is 2 minutes
99 req.setTimeout(1000 * 60 * 10, () => {
100 logger.error('Upload video has timed out.')
101 return res.sendStatus(HttpStatusCode.REQUEST_TIMEOUT_408)
102 })
103
104 const videoPhysicalFile = req.files['videofile'][0]
105 const videoInfo: VideoCreate = req.body
106 const files = req.files
107
108 return addVideo({ res, videoPhysicalFile, videoInfo, files })
109}
110
111export async function addVideoResumable (_req: express.Request, res: express.Response) {
112 const videoPhysicalFile = res.locals.videoFileResumable
113 const videoInfo = videoPhysicalFile.metadata
114 const files = { previewfile: videoInfo.previewfile }
115
116 // Don't need the meta file anymore
117 await deleteResumableUploadMetaFile(videoPhysicalFile.path)
118
119 return addVideo({ res, videoPhysicalFile, videoInfo, files })
120}
121
122async function addVideo (options: {
123 res: express.Response
124 videoPhysicalFile: express.VideoUploadFile
125 videoInfo: VideoCreate
126 files: express.UploadFiles
127}) {
128 const { res, videoPhysicalFile, videoInfo, files } = options
129 const videoChannel = res.locals.videoChannel
130 const user = res.locals.oauth.token.User
131
132 const videoData = buildLocalVideoFromReq(videoInfo, videoChannel.id)
133
134 videoData.state = CONFIG.TRANSCODING.ENABLED
135 ? VideoState.TO_TRANSCODE
136 : VideoState.PUBLISHED
137
138 videoData.duration = videoPhysicalFile.duration // duration was added by a previous middleware
139
140 const video = new VideoModel(videoData) as MVideoFullLight
141 video.VideoChannel = videoChannel
142 video.url = getLocalVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
143
144 const videoFile = await buildNewFile(video, videoPhysicalFile)
145
146 // Move physical file
147 const destination = getVideoFilePath(video, videoFile)
148 await move(videoPhysicalFile.path, destination)
149 // This is important in case if there is another attempt in the retry process
150 videoPhysicalFile.filename = getVideoFilePath(video, videoFile)
151 videoPhysicalFile.path = destination
152
153 const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({
154 video,
155 files,
156 fallback: type => generateVideoMiniature({ video, videoFile, type })
157 })
158
159 const { videoCreated } = await sequelizeTypescript.transaction(async t => {
160 const sequelizeOptions = { transaction: t }
161
162 const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
163
164 await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
165 await videoCreated.addAndSaveThumbnail(previewModel, t)
166
167 // Do not forget to add video channel information to the created video
168 videoCreated.VideoChannel = res.locals.videoChannel
169
170 videoFile.videoId = video.id
171 await videoFile.save(sequelizeOptions)
172
173 video.VideoFiles = [ videoFile ]
174
175 await setVideoTags({ video, tags: videoInfo.tags, transaction: t })
176
177 // Schedule an update in the future?
178 if (videoInfo.scheduleUpdate) {
179 await ScheduleVideoUpdateModel.create({
180 videoId: video.id,
181 updateAt: new Date(videoInfo.scheduleUpdate.updateAt),
182 privacy: videoInfo.scheduleUpdate.privacy || null
183 }, sequelizeOptions)
184 }
185
186 // Channel has a new content, set as updated
187 await videoCreated.VideoChannel.setAsUpdated(t)
188
189 await autoBlacklistVideoIfNeeded({
190 video,
191 user,
192 isRemote: false,
193 isNew: true,
194 transaction: t
195 })
196
197 auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
198 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid, lTags(videoCreated.uuid))
199
200 return { videoCreated }
201 })
202
203 createTorrentFederate(video, videoFile)
204
205 if (video.state === VideoState.TO_TRANSCODE) {
206 await addOptimizeOrMergeAudioJob(videoCreated, videoFile, user)
207 }
208
209 Hooks.runAction('action:api.video.uploaded', { video: videoCreated })
210
211 return res.json({
212 video: {
213 id: videoCreated.id,
214 uuid: videoCreated.uuid
215 }
216 })
217}
218
219async function buildNewFile (video: MVideo, videoPhysicalFile: express.VideoUploadFile) {
220 const videoFile = new VideoFileModel({
221 extname: extname(videoPhysicalFile.filename),
222 size: videoPhysicalFile.size,
223 videoStreamingPlaylistId: null,
224 metadata: await getMetadataFromFile(videoPhysicalFile.path)
225 })
226
227 if (videoFile.isAudio()) {
228 videoFile.resolution = DEFAULT_AUDIO_RESOLUTION
229 } else {
230 videoFile.fps = await getVideoFileFPS(videoPhysicalFile.path)
231 videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).videoFileResolution
232 }
233
234 videoFile.filename = generateVideoFilename(video, false, videoFile.resolution, videoFile.extname)
235
236 return videoFile
237}
238
239async function createTorrentAndSetInfoHashAsync (video: MVideo, fileArg: MVideoFile) {
240 await createTorrentAndSetInfoHash(video, fileArg)
241
242 // Refresh videoFile because the createTorrentAndSetInfoHash could be long
243 const refreshedFile = await VideoFileModel.loadWithVideo(fileArg.id)
244 // File does not exist anymore, remove the generated torrent
245 if (!refreshedFile) return fileArg.removeTorrent()
246
247 refreshedFile.infoHash = fileArg.infoHash
248 refreshedFile.torrentFilename = fileArg.torrentFilename
249
250 return refreshedFile.save()
251}
252
253function createTorrentFederate (video: MVideoFullLight, videoFile: MVideoFile): void {
254 // Create the torrent file in async way because it could be long
255 createTorrentAndSetInfoHashAsync(video, videoFile)
256 .catch(err => logger.error('Cannot create torrent file for video %s', video.url, { err, ...lTags(video.uuid) }))
257 .then(() => VideoModel.loadAndPopulateAccountAndServerAndTags(video.id))
258 .then(refreshedVideo => {
259 if (!refreshedVideo) return
260
261 // Only federate and notify after the torrent creation
262 Notifier.Instance.notifyOnNewVideoIfNeeded(refreshedVideo)
263
264 return retryTransactionWrapper(() => {
265 return sequelizeTypescript.transaction(t => federateVideoIfNeeded(refreshedVideo, true, t))
266 })
267 })
268 .catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err, ...lTags(video.uuid) }))
269}
diff --git a/server/controllers/api/videos/watching.ts b/server/controllers/api/videos/watching.ts
index 627f12aa9..08190e583 100644
--- a/server/controllers/api/videos/watching.ts
+++ b/server/controllers/api/videos/watching.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserWatchingVideo } from '../../../../shared' 2import { UserWatchingVideo } from '../../../../shared'
3import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoWatchingValidator } from '../../../middlewares' 3import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoWatchingValidator } from '../../../middlewares'
4import { UserVideoHistoryModel } from '../../../models/account/user-video-history' 4import { UserVideoHistoryModel } from '../../../models/user/user-video-history'
5import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 5import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
6 6
7const watchingRouter = express.Router() 7const watchingRouter = express.Router()
diff --git a/server/controllers/lazy-static.ts b/server/controllers/lazy-static.ts
index 6f71fdb16..25d3b49b4 100644
--- a/server/controllers/lazy-static.ts
+++ b/server/controllers/lazy-static.ts
@@ -7,7 +7,7 @@ import { LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants'
7import { actorImagePathUnsafeCache, pushActorImageProcessInQueue } from '../lib/actor-image' 7import { actorImagePathUnsafeCache, pushActorImageProcessInQueue } from '../lib/actor-image'
8import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' 8import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache'
9import { asyncMiddleware } from '../middlewares' 9import { asyncMiddleware } from '../middlewares'
10import { ActorImageModel } from '../models/account/actor-image' 10import { ActorImageModel } from '../models/actor/actor-image'
11 11
12const lazyStaticRouter = express.Router() 12const lazyStaticRouter = express.Router()
13 13
diff --git a/server/controllers/services.ts b/server/controllers/services.ts
index 189e1651b..8c0af9ff7 100644
--- a/server/controllers/services.ts
+++ b/server/controllers/services.ts
@@ -78,17 +78,18 @@ function buildOEmbed (options: {
78 const maxWidth = parseInt(req.query.maxwidth, 10) 78 const maxWidth = parseInt(req.query.maxwidth, 10)
79 79
80 const embedUrl = webserverUrl + embedPath 80 const embedUrl = webserverUrl + embedPath
81 let embedWidth = EMBED_SIZE.width
82 let embedHeight = EMBED_SIZE.height
83 const embedTitle = escapeHTML(title) 81 const embedTitle = escapeHTML(title)
84 82
85 let thumbnailUrl = previewPath 83 let thumbnailUrl = previewPath
86 ? webserverUrl + previewPath 84 ? webserverUrl + previewPath
87 : undefined 85 : undefined
88 86
89 if (maxHeight < embedHeight) embedHeight = maxHeight 87 let embedWidth = EMBED_SIZE.width
90 if (maxWidth < embedWidth) embedWidth = maxWidth 88 if (maxWidth < embedWidth) embedWidth = maxWidth
91 89
90 let embedHeight = EMBED_SIZE.height
91 if (maxHeight < embedHeight) embedHeight = maxHeight
92
92 // Our thumbnail is too big for the consumer 93 // Our thumbnail is too big for the consumer
93 if ( 94 if (
94 (maxHeight !== undefined && maxHeight < previewSize.height) || 95 (maxHeight !== undefined && maxHeight < previewSize.height) ||
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 8d9003a3e..8a747ec52 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -2,9 +2,9 @@ import * as cors from 'cors'
2import * as express from 'express' 2import * as express from 'express'
3import { join } from 'path' 3import { join } from 'path'
4import { serveIndexHTML } from '@server/lib/client-html' 4import { serveIndexHTML } from '@server/lib/client-html'
5import { getRegisteredPlugins, getRegisteredThemes } from '@server/lib/config' 5import { getEnabledResolutions, getRegisteredPlugins, getRegisteredThemes } from '@server/lib/config'
6import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 6import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
7import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo' 7import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo/nodeinfo.model'
8import { root } from '../helpers/core-utils' 8import { root } from '../helpers/core-utils'
9import { CONFIG, isEmailEnabled } from '../initializers/config' 9import { CONFIG, isEmailEnabled } from '../initializers/config'
10import { 10import {
@@ -18,10 +18,9 @@ import {
18 WEBSERVER 18 WEBSERVER
19} from '../initializers/constants' 19} from '../initializers/constants'
20import { getThemeOrDefault } from '../lib/plugins/theme-utils' 20import { getThemeOrDefault } from '../lib/plugins/theme-utils'
21import { getEnabledResolutions } from '../lib/video-transcoding'
22import { asyncMiddleware } from '../middlewares' 21import { asyncMiddleware } from '../middlewares'
23import { cacheRoute } from '../middlewares/cache' 22import { cacheRoute } from '../middlewares/cache'
24import { UserModel } from '../models/account/user' 23import { UserModel } from '../models/user/user'
25import { VideoModel } from '../models/video/video' 24import { VideoModel } from '../models/video/video'
26import { VideoCommentModel } from '../models/video/video-comment' 25import { VideoCommentModel } from '../models/video/video-comment'
27 26
diff --git a/server/helpers/actor.ts b/server/helpers/actor.ts
index a60d3ed5d..5f742505b 100644
--- a/server/helpers/actor.ts
+++ b/server/helpers/actor.ts
@@ -1,5 +1,5 @@
1 1
2import { ActorModel } from '../models/activitypub/actor' 2import { ActorModel } from '../models/actor/actor'
3import { MActorAccountChannelId, MActorFull } from '../types/models' 3import { MActorAccountChannelId, MActorFull } from '../types/models'
4 4
5type ActorFetchByUrlType = 'all' | 'association-ids' 5type ActorFetchByUrlType = 'all' | 'association-ids'
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts
index 6aae5e821..884bd187d 100644
--- a/server/helpers/audit-logger.ts
+++ b/server/helpers/audit-logger.ts
@@ -7,7 +7,7 @@ import * as winston from 'winston'
7import { AUDIT_LOG_FILENAME } from '@server/initializers/constants' 7import { AUDIT_LOG_FILENAME } from '@server/initializers/constants'
8import { AdminAbuse, User, VideoChannel, VideoDetails, VideoImport } from '../../shared' 8import { AdminAbuse, User, VideoChannel, VideoDetails, VideoImport } from '../../shared'
9import { CustomConfig } from '../../shared/models/server/custom-config.model' 9import { CustomConfig } from '../../shared/models/server/custom-config.model'
10import { VideoComment } from '../../shared/models/videos/video-comment.model' 10import { VideoComment } from '../../shared/models/videos/comment/video-comment.model'
11import { CONFIG } from '../initializers/config' 11import { CONFIG } from '../initializers/config'
12import { jsonLoggerFormat, labelFormatter } from './logger' 12import { jsonLoggerFormat, labelFormatter } from './logger'
13 13
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts
index fd3b45804..229e9f03c 100644
--- a/server/helpers/custom-validators/misc.ts
+++ b/server/helpers/custom-validators/misc.ts
@@ -14,7 +14,7 @@ function isSafePath (p: string) {
14 }) 14 })
15} 15}
16 16
17function isArray (value: any) { 17function isArray (value: any): value is any[] {
18 return Array.isArray(value) 18 return Array.isArray(value)
19} 19}
20 20
diff --git a/server/helpers/database-utils.ts b/server/helpers/database-utils.ts
index f9cb33aca..7befa2c49 100644
--- a/server/helpers/database-utils.ts
+++ b/server/helpers/database-utils.ts
@@ -68,7 +68,7 @@ function transactionRetryer <T> (func: (err: any, data: T) => any) {
68 }) 68 })
69} 69}
70 70
71function updateInstanceWithAnother <T extends Model<T>> (instanceToUpdate: Model<T>, baseInstance: Model<T>) { 71function updateInstanceWithAnother <M, T extends U, U extends Model<M>> (instanceToUpdate: T, baseInstance: U) {
72 const obj = baseInstance.toJSON() 72 const obj = baseInstance.toJSON()
73 73
74 for (const key of Object.keys(obj)) { 74 for (const key of Object.keys(obj)) {
@@ -88,7 +88,7 @@ function afterCommitIfTransaction (t: Transaction, fn: Function) {
88 return fn() 88 return fn()
89} 89}
90 90
91function deleteNonExistingModels <T extends { hasSameUniqueKeysThan (other: T): boolean } & Model<T>> ( 91function deleteNonExistingModels <T extends { hasSameUniqueKeysThan (other: T): boolean } & Pick<Model, 'destroy'>> (
92 fromDatabase: T[], 92 fromDatabase: T[],
93 newModels: T[], 93 newModels: T[],
94 t: Transaction 94 t: Transaction
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts
index ede22a3cc..010c6961a 100644
--- a/server/helpers/express-utils.ts
+++ b/server/helpers/express-utils.ts
@@ -1,13 +1,13 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as multer from 'multer' 2import * as multer from 'multer'
3import { extname } from 'path'
4import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
5import { CONFIG } from '../initializers/config'
3import { REMOTE_SCHEME } from '../initializers/constants' 6import { REMOTE_SCHEME } from '../initializers/constants'
7import { isArray } from './custom-validators/misc'
4import { logger } from './logger' 8import { logger } from './logger'
5import { deleteFileAndCatch, generateRandomString } from './utils' 9import { deleteFileAndCatch, generateRandomString } from './utils'
6import { extname } from 'path'
7import { isArray } from './custom-validators/misc'
8import { CONFIG } from '../initializers/config'
9import { getExtFromMimetype } from './video' 10import { getExtFromMimetype } from './video'
10import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
11 11
12function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { 12function buildNSFWFilter (res?: express.Response, paramNSFW?: string) {
13 if (paramNSFW === 'true') return true 13 if (paramNSFW === 'true') return true
@@ -30,21 +30,21 @@ function buildNSFWFilter (res?: express.Response, paramNSFW?: string) {
30 return null 30 return null
31} 31}
32 32
33function cleanUpReqFiles (req: { files: { [fieldname: string]: Express.Multer.File[] } | Express.Multer.File[] }) { 33function cleanUpReqFiles (
34 const files = req.files 34 req: { files: { [fieldname: string]: Express.Multer.File[] } | Express.Multer.File[] }
35 35) {
36 if (!files) return 36 const filesObject = req.files
37 if (!filesObject) return
37 38
38 if (isArray(files)) { 39 if (isArray(filesObject)) {
39 (files as Express.Multer.File[]).forEach(f => deleteFileAndCatch(f.path)) 40 filesObject.forEach(f => deleteFileAndCatch(f.path))
40 return 41 return
41 } 42 }
42 43
43 for (const key of Object.keys(files)) { 44 for (const key of Object.keys(filesObject)) {
44 const file = files[key] 45 const files = filesObject[key]
45 46
46 if (isArray(file)) file.forEach(f => deleteFileAndCatch(f.path)) 47 files.forEach(f => deleteFileAndCatch(f.path))
47 else deleteFileAndCatch(file.path)
48 } 48 }
49} 49}
50 50
diff --git a/server/helpers/ffprobe-utils.ts b/server/helpers/ffprobe-utils.ts
index 40eaafd57..ef2aa3f89 100644
--- a/server/helpers/ffprobe-utils.ts
+++ b/server/helpers/ffprobe-utils.ts
@@ -1,6 +1,5 @@
1import * as ffmpeg from 'fluent-ffmpeg' 1import * as ffmpeg from 'fluent-ffmpeg'
2import { VideoFileMetadata } from '@shared/models/videos/video-file-metadata' 2import { getMaxBitrate, VideoFileMetadata, VideoResolution } from '../../shared/models/videos'
3import { getMaxBitrate, VideoResolution } from '../../shared/models/videos'
4import { CONFIG } from '../initializers/config' 3import { CONFIG } from '../initializers/config'
5import { VIDEO_TRANSCODING_FPS } from '../initializers/constants' 4import { VIDEO_TRANSCODING_FPS } from '../initializers/constants'
6import { logger } from './logger' 5import { logger } from './logger'
diff --git a/server/helpers/middlewares/accounts.ts b/server/helpers/middlewares/accounts.ts
index 13ae6cdf4..5addd3e1a 100644
--- a/server/helpers/middlewares/accounts.ts
+++ b/server/helpers/middlewares/accounts.ts
@@ -1,5 +1,5 @@
1import { Response } from 'express' 1import { Response } from 'express'
2import { UserModel } from '@server/models/account/user' 2import { UserModel } from '@server/models/user/user'
3import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' 3import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
4import { AccountModel } from '../../models/account/account' 4import { AccountModel } from '../../models/account/account'
5import { MAccountDefault } from '../../types/models' 5import { MAccountDefault } from '../../types/models'
diff --git a/server/helpers/signup.ts b/server/helpers/signup.ts
index ed872539b..8fa81e601 100644
--- a/server/helpers/signup.ts
+++ b/server/helpers/signup.ts
@@ -1,4 +1,4 @@
1import { UserModel } from '../models/account/user' 1import { UserModel } from '../models/user/user'
2import * as ipaddr from 'ipaddr.js' 2import * as ipaddr from 'ipaddr.js'
3import { CONFIG } from '../initializers/config' 3import { CONFIG } from '../initializers/config'
4 4
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts
index da7e88077..33367f651 100644
--- a/server/helpers/webfinger.ts
+++ b/server/helpers/webfinger.ts
@@ -1,10 +1,10 @@
1import * as WebFinger from 'webfinger.js' 1import * as WebFinger from 'webfinger.js'
2import { WebFingerData } from '../../shared' 2import { WebFingerData } from '../../shared'
3import { ActorModel } from '../models/activitypub/actor'
4import { isTestInstance } from './core-utils'
5import { isActivityPubUrlValid } from './custom-validators/activitypub/misc'
6import { WEBSERVER } from '../initializers/constants' 3import { WEBSERVER } from '../initializers/constants'
4import { ActorModel } from '../models/actor/actor'
7import { MActorFull } from '../types/models' 5import { MActorFull } from '../types/models'
6import { isTestInstance } from './core-utils'
7import { isActivityPubUrlValid } from './custom-validators/activitypub/misc'
8 8
9const webfinger = new WebFinger({ 9const webfinger = new WebFinger({
10 webfist_fallback: false, 10 webfist_fallback: false,
diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts
index fac3da6ba..d003ea3cf 100644
--- a/server/helpers/youtube-dl.ts
+++ b/server/helpers/youtube-dl.ts
@@ -6,7 +6,6 @@ import { CONFIG } from '@server/initializers/config'
6import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' 6import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
7import { VideoResolution } from '../../shared/models/videos' 7import { VideoResolution } from '../../shared/models/videos'
8import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../initializers/constants' 8import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../initializers/constants'
9import { getEnabledResolutions } from '../lib/video-transcoding'
10import { peertubeTruncate, pipelinePromise, root } from './core-utils' 9import { peertubeTruncate, pipelinePromise, root } from './core-utils'
11import { isVideoFileExtnameValid } from './custom-validators/videos' 10import { isVideoFileExtnameValid } from './custom-validators/videos'
12import { logger } from './logger' 11import { logger } from './logger'
@@ -35,361 +34,359 @@ const processOptions = {
35 maxBuffer: 1024 * 1024 * 10 // 10MB 34 maxBuffer: 1024 * 1024 * 10 // 10MB
36} 35}
37 36
38function getYoutubeDLInfo (url: string, opts?: string[]): Promise<YoutubeDLInfo> { 37class YoutubeDL {
39 return new Promise<YoutubeDLInfo>((res, rej) => {
40 let args = opts || [ '-j', '--flat-playlist' ]
41 38
42 if (CONFIG.IMPORT.VIDEOS.HTTP.FORCE_IPV4) { 39 constructor (private readonly url: string = '', private readonly enabledResolutions: number[] = []) {
43 args.push('--force-ipv4')
44 }
45 40
46 args = wrapWithProxyOptions(args) 41 }
47 args = [ '-f', getYoutubeDLVideoFormat() ].concat(args)
48 42
49 safeGetYoutubeDL() 43 getYoutubeDLInfo (opts?: string[]): Promise<YoutubeDLInfo> {
50 .then(youtubeDL => { 44 return new Promise<YoutubeDLInfo>((res, rej) => {
51 youtubeDL.getInfo(url, args, processOptions, (err, info) => { 45 let args = opts || [ '-j', '--flat-playlist' ]
52 if (err) return rej(err)
53 if (info.is_live === true) return rej(new Error('Cannot download a live streaming.'))
54 46
55 const obj = buildVideoInfo(normalizeObject(info)) 47 if (CONFIG.IMPORT.VIDEOS.HTTP.FORCE_IPV4) {
56 if (obj.name && obj.name.length < CONSTRAINTS_FIELDS.VIDEOS.NAME.min) obj.name += ' video' 48 args.push('--force-ipv4')
49 }
57 50
58 return res(obj) 51 args = this.wrapWithProxyOptions(args)
59 }) 52 args = [ '-f', this.getYoutubeDLVideoFormat() ].concat(args)
60 })
61 .catch(err => rej(err))
62 })
63}
64 53
65function getYoutubeDLSubs (url: string, opts?: object): Promise<YoutubeDLSubs> { 54 YoutubeDL.safeGetYoutubeDL()
66 return new Promise<YoutubeDLSubs>((res, rej) => { 55 .then(youtubeDL => {
67 const cwd = CONFIG.STORAGE.TMP_DIR 56 youtubeDL.getInfo(this.url, args, processOptions, (err, info) => {
68 const options = opts || { all: true, format: 'vtt', cwd } 57 if (err) return rej(err)
69 58 if (info.is_live === true) return rej(new Error('Cannot download a live streaming.'))
70 safeGetYoutubeDL()
71 .then(youtubeDL => {
72 youtubeDL.getSubs(url, options, (err, files) => {
73 if (err) return rej(err)
74 if (!files) return []
75
76 logger.debug('Get subtitles from youtube dl.', { url, files })
77
78 const subtitles = files.reduce((acc, filename) => {
79 const matched = filename.match(/\.([a-z]{2})(-[a-z]+)?\.(vtt|ttml)/i)
80 if (!matched || !matched[1]) return acc
81
82 return [
83 ...acc,
84 {
85 language: matched[1],
86 path: join(cwd, filename),
87 filename
88 }
89 ]
90 }, [])
91 59
92 return res(subtitles) 60 const obj = this.buildVideoInfo(this.normalizeObject(info))
61 if (obj.name && obj.name.length < CONSTRAINTS_FIELDS.VIDEOS.NAME.min) obj.name += ' video'
62
63 return res(obj)
64 })
93 }) 65 })
94 }) 66 .catch(err => rej(err))
95 .catch(err => rej(err)) 67 })
96 }) 68 }
97}
98 69
99function getYoutubeDLVideoFormat () { 70 getYoutubeDLSubs (opts?: object): Promise<YoutubeDLSubs> {
100 /** 71 return new Promise<YoutubeDLSubs>((res, rej) => {
101 * list of format selectors in order or preference 72 const cwd = CONFIG.STORAGE.TMP_DIR
102 * see https://github.com/ytdl-org/youtube-dl#format-selection 73 const options = opts || { all: true, format: 'vtt', cwd }
103 * 74
104 * case #1 asks for a mp4 using h264 (avc1) and the exact resolution in the hope 75 YoutubeDL.safeGetYoutubeDL()
105 * of being able to do a "quick-transcode" 76 .then(youtubeDL => {
106 * case #2 is the first fallback. No "quick-transcode" means we can get anything else (like vp9) 77 youtubeDL.getSubs(this.url, options, (err, files) => {
107 * case #3 is the resolution-degraded equivalent of #1, and already a pretty safe fallback 78 if (err) return rej(err)
108 * 79 if (!files) return []
109 * in any case we avoid AV1, see https://github.com/Chocobozzz/PeerTube/issues/3499 80
110 **/ 81 logger.debug('Get subtitles from youtube dl.', { url: this.url, files })
111 const enabledResolutions = getEnabledResolutions('vod') 82
112 const resolution = enabledResolutions.length === 0 83 const subtitles = files.reduce((acc, filename) => {
113 ? VideoResolution.H_720P 84 const matched = filename.match(/\.([a-z]{2})(-[a-z]+)?\.(vtt|ttml)/i)
114 : Math.max(...enabledResolutions) 85 if (!matched || !matched[1]) return acc
115 86
116 return [ 87 return [
117 `bestvideo[vcodec^=avc1][height=${resolution}]+bestaudio[ext=m4a]`, // case #1 88 ...acc,
118 `bestvideo[vcodec!*=av01][vcodec!*=vp9.2][height=${resolution}]+bestaudio`, // case #2 89 {
119 `bestvideo[vcodec^=avc1][height<=${resolution}]+bestaudio[ext=m4a]`, // case #3 90 language: matched[1],
120 `bestvideo[vcodec!*=av01][vcodec!*=vp9.2]+bestaudio`, 91 path: join(cwd, filename),
121 'best[vcodec!*=av01][vcodec!*=vp9.2]', // case fallback for known formats 92 filename
122 'best' // Ultimate fallback 93 }
123 ].join('/') 94 ]
124} 95 }, [])
96
97 return res(subtitles)
98 })
99 })
100 .catch(err => rej(err))
101 })
102 }
125 103
126function downloadYoutubeDLVideo (url: string, fileExt: string, timeout: number) { 104 getYoutubeDLVideoFormat () {
127 // Leave empty the extension, youtube-dl will add it 105 /**
128 const pathWithoutExtension = generateVideoImportTmpPath(url, '') 106 * list of format selectors in order or preference
107 * see https://github.com/ytdl-org/youtube-dl#format-selection
108 *
109 * case #1 asks for a mp4 using h264 (avc1) and the exact resolution in the hope
110 * of being able to do a "quick-transcode"
111 * case #2 is the first fallback. No "quick-transcode" means we can get anything else (like vp9)
112 * case #3 is the resolution-degraded equivalent of #1, and already a pretty safe fallback
113 *
114 * in any case we avoid AV1, see https://github.com/Chocobozzz/PeerTube/issues/3499
115 **/
116 const resolution = this.enabledResolutions.length === 0
117 ? VideoResolution.H_720P
118 : Math.max(...this.enabledResolutions)
119
120 return [
121 `bestvideo[vcodec^=avc1][height=${resolution}]+bestaudio[ext=m4a]`, // case #1
122 `bestvideo[vcodec!*=av01][vcodec!*=vp9.2][height=${resolution}]+bestaudio`, // case #2
123 `bestvideo[vcodec^=avc1][height<=${resolution}]+bestaudio[ext=m4a]`, // case #3
124 `bestvideo[vcodec!*=av01][vcodec!*=vp9.2]+bestaudio`,
125 'best[vcodec!*=av01][vcodec!*=vp9.2]', // case fallback for known formats
126 'best' // Ultimate fallback
127 ].join('/')
128 }
129 129
130 let timer 130 downloadYoutubeDLVideo (fileExt: string, timeout: number) {
131 // Leave empty the extension, youtube-dl will add it
132 const pathWithoutExtension = generateVideoImportTmpPath(this.url, '')
131 133
132 logger.info('Importing youtubeDL video %s to %s', url, pathWithoutExtension) 134 let timer
133 135
134 let options = [ '-f', getYoutubeDLVideoFormat(), '-o', pathWithoutExtension ] 136 logger.info('Importing youtubeDL video %s to %s', this.url, pathWithoutExtension)
135 options = wrapWithProxyOptions(options)
136 137
137 if (process.env.FFMPEG_PATH) { 138 let options = [ '-f', this.getYoutubeDLVideoFormat(), '-o', pathWithoutExtension ]
138 options = options.concat([ '--ffmpeg-location', process.env.FFMPEG_PATH ]) 139 options = this.wrapWithProxyOptions(options)
139 }
140 140
141 logger.debug('YoutubeDL options for %s.', url, { options }) 141 if (process.env.FFMPEG_PATH) {
142 options = options.concat([ '--ffmpeg-location', process.env.FFMPEG_PATH ])
143 }
142 144
143 return new Promise<string>((res, rej) => { 145 logger.debug('YoutubeDL options for %s.', this.url, { options })
144 safeGetYoutubeDL()
145 .then(youtubeDL => {
146 youtubeDL.exec(url, options, processOptions, async err => {
147 clearTimeout(timer)
148 146
149 try { 147 return new Promise<string>((res, rej) => {
150 // If youtube-dl did not guess an extension for our file, just use .mp4 as default 148 YoutubeDL.safeGetYoutubeDL()
151 if (await pathExists(pathWithoutExtension)) { 149 .then(youtubeDL => {
152 await move(pathWithoutExtension, pathWithoutExtension + '.mp4') 150 youtubeDL.exec(this.url, options, processOptions, async err => {
153 } 151 clearTimeout(timer)
152
153 try {
154 // If youtube-dl did not guess an extension for our file, just use .mp4 as default
155 if (await pathExists(pathWithoutExtension)) {
156 await move(pathWithoutExtension, pathWithoutExtension + '.mp4')
157 }
154 158
155 const path = await guessVideoPathWithExtension(pathWithoutExtension, fileExt) 159 const path = await this.guessVideoPathWithExtension(pathWithoutExtension, fileExt)
156 160
157 if (err) { 161 if (err) {
158 remove(path) 162 remove(path)
159 .catch(err => logger.error('Cannot delete path on YoutubeDL error.', { err })) 163 .catch(err => logger.error('Cannot delete path on YoutubeDL error.', { err }))
160 164
165 return rej(err)
166 }
167
168 return res(path)
169 } catch (err) {
161 return rej(err) 170 return rej(err)
162 } 171 }
163 172 })
164 return res(path) 173
165 } catch (err) { 174 timer = setTimeout(() => {
166 return rej(err) 175 const err = new Error('YoutubeDL download timeout.')
167 } 176
177 this.guessVideoPathWithExtension(pathWithoutExtension, fileExt)
178 .then(path => remove(path))
179 .finally(() => rej(err))
180 .catch(err => {
181 logger.error('Cannot remove file in youtubeDL timeout.', { err })
182 return rej(err)
183 })
184 }, timeout)
168 }) 185 })
186 .catch(err => rej(err))
187 })
188 }
169 189
170 timer = setTimeout(() => { 190 buildOriginallyPublishedAt (obj: any) {
171 const err = new Error('YoutubeDL download timeout.') 191 let originallyPublishedAt: Date = null
172 192
173 guessVideoPathWithExtension(pathWithoutExtension, fileExt) 193 const uploadDateMatcher = /^(\d{4})(\d{2})(\d{2})$/.exec(obj.upload_date)
174 .then(path => remove(path)) 194 if (uploadDateMatcher) {
175 .finally(() => rej(err)) 195 originallyPublishedAt = new Date()
176 .catch(err => { 196 originallyPublishedAt.setHours(0, 0, 0, 0)
177 logger.error('Cannot remove file in youtubeDL timeout.', { err })
178 return rej(err)
179 })
180 }, timeout)
181 })
182 .catch(err => rej(err))
183 })
184}
185
186// Thanks: https://github.com/przemyslawpluta/node-youtube-dl/blob/master/lib/downloader.js
187// We rewrote it to avoid sync calls
188async function updateYoutubeDLBinary () {
189 logger.info('Updating youtubeDL binary.')
190 197
191 const binDirectory = join(root(), 'node_modules', 'youtube-dl', 'bin') 198 const year = parseInt(uploadDateMatcher[1], 10)
192 const bin = join(binDirectory, 'youtube-dl') 199 // Month starts from 0
193 const detailsPath = join(binDirectory, 'details') 200 const month = parseInt(uploadDateMatcher[2], 10) - 1
194 const url = process.env.YOUTUBE_DL_DOWNLOAD_HOST || 'https://yt-dl.org/downloads/latest/youtube-dl' 201 const day = parseInt(uploadDateMatcher[3], 10)
195 202
196 await ensureDir(binDirectory) 203 originallyPublishedAt.setFullYear(year, month, day)
204 }
197 205
198 try { 206 return originallyPublishedAt
199 const result = await got(url, { followRedirect: false }) 207 }
200 208
201 if (result.statusCode !== HttpStatusCode.FOUND_302) { 209 private async guessVideoPathWithExtension (tmpPath: string, sourceExt: string) {
202 logger.error('youtube-dl update error: did not get redirect for the latest version link. Status %d', result.statusCode) 210 if (!isVideoFileExtnameValid(sourceExt)) {
203 return 211 throw new Error('Invalid video extension ' + sourceExt)
204 } 212 }
205 213
206 const newUrl = result.headers.location 214 const extensions = [ sourceExt, '.mp4', '.mkv', '.webm' ]
207 const newVersion = /yt-dl\.org\/downloads\/(\d{4}\.\d\d\.\d\d(\.\d)?)\/youtube-dl/.exec(newUrl)[1]
208 215
209 const downloadFileStream = got.stream(newUrl) 216 for (const extension of extensions) {
210 const writeStream = createWriteStream(bin, { mode: 493 }) 217 const path = tmpPath + extension
211 218
212 await pipelinePromise( 219 if (await pathExists(path)) return path
213 downloadFileStream, 220 }
214 writeStream
215 )
216
217 const details = JSON.stringify({ version: newVersion, path: bin, exec: 'youtube-dl' })
218 await writeFile(detailsPath, details, { encoding: 'utf8' })
219 221
220 logger.info('youtube-dl updated to version %s.', newVersion) 222 throw new Error('Cannot guess path of ' + tmpPath)
221 } catch (err) {
222 logger.error('Cannot update youtube-dl.', { err })
223 } 223 }
224}
225 224
226async function safeGetYoutubeDL () { 225 private normalizeObject (obj: any) {
227 let youtubeDL 226 const newObj: any = {}
228 227
229 try { 228 for (const key of Object.keys(obj)) {
230 youtubeDL = require('youtube-dl') 229 // Deprecated key
231 } catch (e) { 230 if (key === 'resolution') continue
232 // Download binary
233 await updateYoutubeDLBinary()
234 youtubeDL = require('youtube-dl')
235 }
236 231
237 return youtubeDL 232 const value = obj[key]
238}
239 233
240function buildOriginallyPublishedAt (obj: any) { 234 if (typeof value === 'string') {
241 let originallyPublishedAt: Date = null 235 newObj[key] = value.normalize()
236 } else {
237 newObj[key] = value
238 }
239 }
242 240
243 const uploadDateMatcher = /^(\d{4})(\d{2})(\d{2})$/.exec(obj.upload_date) 241 return newObj
244 if (uploadDateMatcher) { 242 }
245 originallyPublishedAt = new Date()
246 originallyPublishedAt.setHours(0, 0, 0, 0)
247 243
248 const year = parseInt(uploadDateMatcher[1], 10) 244 private buildVideoInfo (obj: any): YoutubeDLInfo {
249 // Month starts from 0 245 return {
250 const month = parseInt(uploadDateMatcher[2], 10) - 1 246 name: this.titleTruncation(obj.title),
251 const day = parseInt(uploadDateMatcher[3], 10) 247 description: this.descriptionTruncation(obj.description),
248 category: this.getCategory(obj.categories),
249 licence: this.getLicence(obj.license),
250 language: this.getLanguage(obj.language),
251 nsfw: this.isNSFW(obj),
252 tags: this.getTags(obj.tags),
253 thumbnailUrl: obj.thumbnail || undefined,
254 originallyPublishedAt: this.buildOriginallyPublishedAt(obj),
255 ext: obj.ext
256 }
257 }
252 258
253 originallyPublishedAt.setFullYear(year, month, day) 259 private titleTruncation (title: string) {
260 return peertubeTruncate(title, {
261 length: CONSTRAINTS_FIELDS.VIDEOS.NAME.max,
262 separator: /,? +/,
263 omission: ' […]'
264 })
254 } 265 }
255 266
256 return originallyPublishedAt 267 private descriptionTruncation (description: string) {
257} 268 if (!description || description.length < CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.min) return undefined
258 269
259// --------------------------------------------------------------------------- 270 return peertubeTruncate(description, {
271 length: CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max,
272 separator: /,? +/,
273 omission: ' […]'
274 })
275 }
260 276
261export { 277 private isNSFW (info: any) {
262 updateYoutubeDLBinary, 278 return info.age_limit && info.age_limit >= 16
263 getYoutubeDLVideoFormat, 279 }
264 downloadYoutubeDLVideo,
265 getYoutubeDLSubs,
266 getYoutubeDLInfo,
267 safeGetYoutubeDL,
268 buildOriginallyPublishedAt
269}
270 280
271// --------------------------------------------------------------------------- 281 private getTags (tags: any) {
282 if (Array.isArray(tags) === false) return []
272 283
273async function guessVideoPathWithExtension (tmpPath: string, sourceExt: string) { 284 return tags
274 if (!isVideoFileExtnameValid(sourceExt)) { 285 .filter(t => t.length < CONSTRAINTS_FIELDS.VIDEOS.TAG.max && t.length > CONSTRAINTS_FIELDS.VIDEOS.TAG.min)
275 throw new Error('Invalid video extension ' + sourceExt) 286 .map(t => t.normalize())
287 .slice(0, 5)
276 } 288 }
277 289
278 const extensions = [ sourceExt, '.mp4', '.mkv', '.webm' ] 290 private getLicence (licence: string) {
291 if (!licence) return undefined
279 292
280 for (const extension of extensions) { 293 if (licence.includes('Creative Commons Attribution')) return 1
281 const path = tmpPath + extension
282 294
283 if (await pathExists(path)) return path 295 for (const key of Object.keys(VIDEO_LICENCES)) {
284 } 296 const peertubeLicence = VIDEO_LICENCES[key]
297 if (peertubeLicence.toLowerCase() === licence.toLowerCase()) return parseInt(key, 10)
298 }
285 299
286 throw new Error('Cannot guess path of ' + tmpPath) 300 return undefined
287} 301 }
288 302
289function normalizeObject (obj: any) { 303 private getCategory (categories: string[]) {
290 const newObj: any = {} 304 if (!categories) return undefined
291 305
292 for (const key of Object.keys(obj)) { 306 const categoryString = categories[0]
293 // Deprecated key 307 if (!categoryString || typeof categoryString !== 'string') return undefined
294 if (key === 'resolution') continue
295 308
296 const value = obj[key] 309 if (categoryString === 'News & Politics') return 11
297 310
298 if (typeof value === 'string') { 311 for (const key of Object.keys(VIDEO_CATEGORIES)) {
299 newObj[key] = value.normalize() 312 const category = VIDEO_CATEGORIES[key]
300 } else { 313 if (categoryString.toLowerCase() === category.toLowerCase()) return parseInt(key, 10)
301 newObj[key] = value
302 } 314 }
303 }
304 315
305 return newObj 316 return undefined
306}
307
308function buildVideoInfo (obj: any): YoutubeDLInfo {
309 return {
310 name: titleTruncation(obj.title),
311 description: descriptionTruncation(obj.description),
312 category: getCategory(obj.categories),
313 licence: getLicence(obj.license),
314 language: getLanguage(obj.language),
315 nsfw: isNSFW(obj),
316 tags: getTags(obj.tags),
317 thumbnailUrl: obj.thumbnail || undefined,
318 originallyPublishedAt: buildOriginallyPublishedAt(obj),
319 ext: obj.ext
320 } 317 }
321}
322 318
323function titleTruncation (title: string) { 319 private getLanguage (language: string) {
324 return peertubeTruncate(title, { 320 return VIDEO_LANGUAGES[language] ? language : undefined
325 length: CONSTRAINTS_FIELDS.VIDEOS.NAME.max, 321 }
326 separator: /,? +/,
327 omission: ' […]'
328 })
329}
330 322
331function descriptionTruncation (description: string) { 323 private wrapWithProxyOptions (options: string[]) {
332 if (!description || description.length < CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.min) return undefined 324 if (CONFIG.IMPORT.VIDEOS.HTTP.PROXY.ENABLED) {
325 logger.debug('Using proxy for YoutubeDL')
333 326
334 return peertubeTruncate(description, { 327 return [ '--proxy', CONFIG.IMPORT.VIDEOS.HTTP.PROXY.URL ].concat(options)
335 length: CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max, 328 }
336 separator: /,? +/,
337 omission: ' […]'
338 })
339}
340 329
341function isNSFW (info: any) { 330 return options
342 return info.age_limit && info.age_limit >= 16 331 }
343}
344 332
345function getTags (tags: any) { 333 // Thanks: https://github.com/przemyslawpluta/node-youtube-dl/blob/master/lib/downloader.js
346 if (Array.isArray(tags) === false) return [] 334 // We rewrote it to avoid sync calls
335 static async updateYoutubeDLBinary () {
336 logger.info('Updating youtubeDL binary.')
347 337
348 return tags 338 const binDirectory = join(root(), 'node_modules', 'youtube-dl', 'bin')
349 .filter(t => t.length < CONSTRAINTS_FIELDS.VIDEOS.TAG.max && t.length > CONSTRAINTS_FIELDS.VIDEOS.TAG.min) 339 const bin = join(binDirectory, 'youtube-dl')
350 .map(t => t.normalize()) 340 const detailsPath = join(binDirectory, 'details')
351 .slice(0, 5) 341 const url = process.env.YOUTUBE_DL_DOWNLOAD_HOST || 'https://yt-dl.org/downloads/latest/youtube-dl'
352}
353 342
354function getLicence (licence: string) { 343 await ensureDir(binDirectory)
355 if (!licence) return undefined
356 344
357 if (licence.includes('Creative Commons Attribution')) return 1 345 try {
346 const result = await got(url, { followRedirect: false })
358 347
359 for (const key of Object.keys(VIDEO_LICENCES)) { 348 if (result.statusCode !== HttpStatusCode.FOUND_302) {
360 const peertubeLicence = VIDEO_LICENCES[key] 349 logger.error('youtube-dl update error: did not get redirect for the latest version link. Status %d', result.statusCode)
361 if (peertubeLicence.toLowerCase() === licence.toLowerCase()) return parseInt(key, 10) 350 return
362 } 351 }
363 352
364 return undefined 353 const newUrl = result.headers.location
365} 354 const newVersion = /yt-dl\.org\/downloads\/(\d{4}\.\d\d\.\d\d(\.\d)?)\/youtube-dl/.exec(newUrl)[1]
366 355
367function getCategory (categories: string[]) { 356 const downloadFileStream = got.stream(newUrl)
368 if (!categories) return undefined 357 const writeStream = createWriteStream(bin, { mode: 493 })
369 358
370 const categoryString = categories[0] 359 await pipelinePromise(
371 if (!categoryString || typeof categoryString !== 'string') return undefined 360 downloadFileStream,
361 writeStream
362 )
372 363
373 if (categoryString === 'News & Politics') return 11 364 const details = JSON.stringify({ version: newVersion, path: bin, exec: 'youtube-dl' })
365 await writeFile(detailsPath, details, { encoding: 'utf8' })
374 366
375 for (const key of Object.keys(VIDEO_CATEGORIES)) { 367 logger.info('youtube-dl updated to version %s.', newVersion)
376 const category = VIDEO_CATEGORIES[key] 368 } catch (err) {
377 if (categoryString.toLowerCase() === category.toLowerCase()) return parseInt(key, 10) 369 logger.error('Cannot update youtube-dl.', { err })
370 }
378 } 371 }
379 372
380 return undefined 373 static async safeGetYoutubeDL () {
381} 374 let youtubeDL
382
383function getLanguage (language: string) {
384 return VIDEO_LANGUAGES[language] ? language : undefined
385}
386 375
387function wrapWithProxyOptions (options: string[]) { 376 try {
388 if (CONFIG.IMPORT.VIDEOS.HTTP.PROXY.ENABLED) { 377 youtubeDL = require('youtube-dl')
389 logger.debug('Using proxy for YoutubeDL') 378 } catch (e) {
379 // Download binary
380 await this.updateYoutubeDLBinary()
381 youtubeDL = require('youtube-dl')
382 }
390 383
391 return [ '--proxy', CONFIG.IMPORT.VIDEOS.HTTP.PROXY.URL ].concat(options) 384 return youtubeDL
392 } 385 }
386}
387
388// ---------------------------------------------------------------------------
393 389
394 return options 390export {
391 YoutubeDL
395} 392}
diff --git a/server/initializers/checker-after-init.ts b/server/initializers/checker-after-init.ts
index 5fd1af82f..d75c0af04 100644
--- a/server/initializers/checker-after-init.ts
+++ b/server/initializers/checker-after-init.ts
@@ -7,7 +7,7 @@ import { RecentlyAddedStrategy } from '../../shared/models/redundancy'
7import { isProdInstance, isTestInstance, parseSemVersion } from '../helpers/core-utils' 7import { isProdInstance, isTestInstance, parseSemVersion } from '../helpers/core-utils'
8import { isArray } from '../helpers/custom-validators/misc' 8import { isArray } from '../helpers/custom-validators/misc'
9import { logger } from '../helpers/logger' 9import { logger } from '../helpers/logger'
10import { UserModel } from '../models/account/user' 10import { UserModel } from '../models/user/user'
11import { ApplicationModel, getServerActor } from '../models/application/application' 11import { ApplicationModel, getServerActor } from '../models/application/application'
12import { OAuthClientModel } from '../models/oauth/oauth-client' 12import { OAuthClientModel } from '../models/oauth/oauth-client'
13import { CONFIG, isEmailEnabled } from './config' 13import { CONFIG, isEmailEnabled } from './config'
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 6f388420e..4cf7dcf0a 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -702,7 +702,8 @@ const CUSTOM_HTML_TAG_COMMENTS = {
702 TITLE: '<!-- title tag -->', 702 TITLE: '<!-- title tag -->',
703 DESCRIPTION: '<!-- description tag -->', 703 DESCRIPTION: '<!-- description tag -->',
704 CUSTOM_CSS: '<!-- custom css tag -->', 704 CUSTOM_CSS: '<!-- custom css tag -->',
705 META_TAGS: '<!-- meta tags -->' 705 META_TAGS: '<!-- meta tags -->',
706 SERVER_CONFIG: '<!-- server config -->'
706} 707}
707 708
708// --------------------------------------------------------------------------- 709// ---------------------------------------------------------------------------
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index edf12bc41..75a13ec8b 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -2,6 +2,9 @@ import { QueryTypes, Transaction } from 'sequelize'
2import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' 2import { Sequelize as SequelizeTypescript } from 'sequelize-typescript'
3import { TrackerModel } from '@server/models/server/tracker' 3import { TrackerModel } from '@server/models/server/tracker'
4import { VideoTrackerModel } from '@server/models/server/video-tracker' 4import { VideoTrackerModel } from '@server/models/server/video-tracker'
5import { UserModel } from '@server/models/user/user'
6import { UserNotificationModel } from '@server/models/user/user-notification'
7import { UserVideoHistoryModel } from '@server/models/user/user-video-history'
5import { isTestInstance } from '../helpers/core-utils' 8import { isTestInstance } from '../helpers/core-utils'
6import { logger } from '../helpers/logger' 9import { logger } from '../helpers/logger'
7import { AbuseModel } from '../models/abuse/abuse' 10import { AbuseModel } from '../models/abuse/abuse'
@@ -11,13 +14,9 @@ import { VideoCommentAbuseModel } from '../models/abuse/video-comment-abuse'
11import { AccountModel } from '../models/account/account' 14import { AccountModel } from '../models/account/account'
12import { AccountBlocklistModel } from '../models/account/account-blocklist' 15import { AccountBlocklistModel } from '../models/account/account-blocklist'
13import { AccountVideoRateModel } from '../models/account/account-video-rate' 16import { AccountVideoRateModel } from '../models/account/account-video-rate'
14import { ActorImageModel } from '../models/account/actor-image' 17import { ActorModel } from '../models/actor/actor'
15import { UserModel } from '../models/account/user' 18import { ActorFollowModel } from '../models/actor/actor-follow'
16import { UserNotificationModel } from '../models/account/user-notification' 19import { ActorImageModel } from '../models/actor/actor-image'
17import { UserNotificationSettingModel } from '../models/account/user-notification-setting'
18import { UserVideoHistoryModel } from '../models/account/user-video-history'
19import { ActorModel } from '../models/activitypub/actor'
20import { ActorFollowModel } from '../models/activitypub/actor-follow'
21import { ApplicationModel } from '../models/application/application' 20import { ApplicationModel } from '../models/application/application'
22import { OAuthClientModel } from '../models/oauth/oauth-client' 21import { OAuthClientModel } from '../models/oauth/oauth-client'
23import { OAuthTokenModel } from '../models/oauth/oauth-token' 22import { OAuthTokenModel } from '../models/oauth/oauth-token'
@@ -25,6 +24,7 @@ import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
25import { PluginModel } from '../models/server/plugin' 24import { PluginModel } from '../models/server/plugin'
26import { ServerModel } from '../models/server/server' 25import { ServerModel } from '../models/server/server'
27import { ServerBlocklistModel } from '../models/server/server-blocklist' 26import { ServerBlocklistModel } from '../models/server/server-blocklist'
27import { UserNotificationSettingModel } from '../models/user/user-notification-setting'
28import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update' 28import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update'
29import { TagModel } from '../models/video/tag' 29import { TagModel } from '../models/video/tag'
30import { ThumbnailModel } from '../models/video/thumbnail' 30import { ThumbnailModel } from '../models/video/thumbnail'
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts
index 8dcff64e2..676f88653 100644
--- a/server/initializers/installer.ts
+++ b/server/initializers/installer.ts
@@ -2,7 +2,7 @@ import * as passwordGenerator from 'password-generator'
2import { UserRole } from '../../shared' 2import { UserRole } from '../../shared'
3import { logger } from '../helpers/logger' 3import { logger } from '../helpers/logger'
4import { createApplicationActor, createUserAccountAndChannelAndPlaylist } from '../lib/user' 4import { createApplicationActor, createUserAccountAndChannelAndPlaylist } from '../lib/user'
5import { UserModel } from '../models/account/user' 5import { UserModel } from '../models/user/user'
6import { ApplicationModel } from '../models/application/application' 6import { ApplicationModel } from '../models/application/application'
7import { OAuthClientModel } from '../models/oauth/oauth-client' 7import { OAuthClientModel } from '../models/oauth/oauth-client'
8import { applicationExist, clientsExist, usersExist } from './checker-after-init' 8import { applicationExist, clientsExist, usersExist } from './checker-after-init'
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index 5fe7381c9..1bcee7ef9 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -20,8 +20,8 @@ import { getUrlFromWebfinger } from '../../helpers/webfinger'
20import { MIMETYPES, WEBSERVER } from '../../initializers/constants' 20import { MIMETYPES, WEBSERVER } from '../../initializers/constants'
21import { sequelizeTypescript } from '../../initializers/database' 21import { sequelizeTypescript } from '../../initializers/database'
22import { AccountModel } from '../../models/account/account' 22import { AccountModel } from '../../models/account/account'
23import { ActorImageModel } from '../../models/account/actor-image' 23import { ActorModel } from '../../models/actor/actor'
24import { ActorModel } from '../../models/activitypub/actor' 24import { ActorImageModel } from '../../models/actor/actor-image'
25import { ServerModel } from '../../models/server/server' 25import { ServerModel } from '../../models/server/server'
26import { VideoChannelModel } from '../../models/video/video-channel' 26import { VideoChannelModel } from '../../models/video/video-channel'
27import { 27import {
@@ -132,12 +132,11 @@ async function getOrCreateActorAndServerAndModel (
132 return actorRefreshed 132 return actorRefreshed
133} 133}
134 134
135function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) { 135function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string) {
136 return new ActorModel({ 136 return new ActorModel({
137 type, 137 type,
138 url, 138 url,
139 preferredUsername, 139 preferredUsername,
140 uuid,
141 publicKey: null, 140 publicKey: null,
142 privateKey: null, 141 privateKey: null,
143 followersCount: 0, 142 followersCount: 0,
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts
index 2986714d3..d0558f191 100644
--- a/server/lib/activitypub/audience.ts
+++ b/server/lib/activitypub/audience.ts
@@ -1,7 +1,7 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience } from '../../../shared/models/activitypub' 2import { ActivityAudience } from '../../../shared/models/activitypub'
3import { ACTIVITY_PUB } from '../../initializers/constants' 3import { ACTIVITY_PUB } from '../../initializers/constants'
4import { ActorModel } from '../../models/activitypub/actor' 4import { ActorModel } from '../../models/actor/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, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '../../types/models' 7import { MActorFollowersUrl, MActorLight, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '../../types/models'
diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts
index 1799829f8..8ad470cf4 100644
--- a/server/lib/activitypub/process/process-accept.ts
+++ b/server/lib/activitypub/process/process-accept.ts
@@ -1,8 +1,8 @@
1import { ActivityAccept } from '../../../../shared/models/activitypub' 1import { ActivityAccept } from '../../../../shared/models/activitypub'
2import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 2import { ActorFollowModel } from '../../../models/actor/actor-follow'
3import { addFetchOutboxJob } from '../actor'
4import { APProcessorOptions } from '../../../types/activitypub-processor.model' 3import { APProcessorOptions } from '../../../types/activitypub-processor.model'
5import { MActorDefault, MActorSignature } from '../../../types/models' 4import { MActorDefault, MActorSignature } from '../../../types/models'
5import { addFetchOutboxJob } from '../actor'
6 6
7async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) { 7async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) {
8 const { byActor: targetActor, inboxActor } = options 8 const { byActor: targetActor, inboxActor } = options
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts
index 88a968318..20214246c 100644
--- a/server/lib/activitypub/process/process-delete.ts
+++ b/server/lib/activitypub/process/process-delete.ts
@@ -2,7 +2,7 @@ import { ActivityDelete } from '../../../../shared/models/activitypub'
2import { retryTransactionWrapper } from '../../../helpers/database-utils' 2import { retryTransactionWrapper } from '../../../helpers/database-utils'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { sequelizeTypescript } from '../../../initializers/database' 4import { sequelizeTypescript } from '../../../initializers/database'
5import { ActorModel } from '../../../models/activitypub/actor' 5import { ActorModel } from '../../../models/actor/actor'
6import { VideoModel } from '../../../models/video/video' 6import { VideoModel } from '../../../models/video/video'
7import { VideoCommentModel } from '../../../models/video/video-comment' 7import { VideoCommentModel } from '../../../models/video/video-comment'
8import { VideoPlaylistModel } from '../../../models/video/video-playlist' 8import { VideoPlaylistModel } from '../../../models/video/video-playlist'
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
index 38d684512..9009c6469 100644
--- a/server/lib/activitypub/process/process-follow.ts
+++ b/server/lib/activitypub/process/process-follow.ts
@@ -1,17 +1,17 @@
1import { getServerActor } from '@server/models/application/application'
1import { ActivityFollow } from '../../../../shared/models/activitypub' 2import { ActivityFollow } from '../../../../shared/models/activitypub'
3import { getAPId } from '../../../helpers/activitypub'
2import { retryTransactionWrapper } from '../../../helpers/database-utils' 4import { retryTransactionWrapper } from '../../../helpers/database-utils'
3import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
4import { sequelizeTypescript } from '../../../initializers/database'
5import { ActorModel } from '../../../models/activitypub/actor'
6import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
7import { sendAccept, sendReject } from '../send'
8import { Notifier } from '../../notifier'
9import { getAPId } from '../../../helpers/activitypub'
10import { CONFIG } from '../../../initializers/config' 6import { CONFIG } from '../../../initializers/config'
7import { sequelizeTypescript } from '../../../initializers/database'
8import { ActorModel } from '../../../models/actor/actor'
9import { ActorFollowModel } from '../../../models/actor/actor-follow'
11import { APProcessorOptions } from '../../../types/activitypub-processor.model' 10import { APProcessorOptions } from '../../../types/activitypub-processor.model'
12import { MActorFollowActors, MActorSignature } from '../../../types/models' 11import { MActorFollowActors, MActorSignature } from '../../../types/models'
12import { Notifier } from '../../notifier'
13import { autoFollowBackIfNeeded } from '../follow' 13import { autoFollowBackIfNeeded } from '../follow'
14import { getServerActor } from '@server/models/application/application' 14import { sendAccept, sendReject } from '../send'
15 15
16async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) { 16async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) {
17 const { activity, byActor } = options 17 const { activity, byActor } = options
diff --git a/server/lib/activitypub/process/process-reject.ts b/server/lib/activitypub/process/process-reject.ts
index 03b669fd9..7f7ab305f 100644
--- a/server/lib/activitypub/process/process-reject.ts
+++ b/server/lib/activitypub/process/process-reject.ts
@@ -1,6 +1,6 @@
1import { ActivityReject } from '../../../../shared/models/activitypub/activity' 1import { ActivityReject } from '../../../../shared/models/activitypub/activity'
2import { sequelizeTypescript } from '../../../initializers/database' 2import { sequelizeTypescript } from '../../../initializers/database'
3import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 3import { ActorFollowModel } from '../../../models/actor/actor-follow'
4import { APProcessorOptions } from '../../../types/activitypub-processor.model' 4import { APProcessorOptions } from '../../../types/activitypub-processor.model'
5import { MActor } from '../../../types/models' 5import { MActor } from '../../../types/models'
6 6
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts
index e520c2f0d..9f031b528 100644
--- a/server/lib/activitypub/process/process-undo.ts
+++ b/server/lib/activitypub/process/process-undo.ts
@@ -4,8 +4,8 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
5import { sequelizeTypescript } from '../../../initializers/database' 5import { sequelizeTypescript } from '../../../initializers/database'
6import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 6import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
7import { ActorModel } from '../../../models/activitypub/actor' 7import { ActorModel } from '../../../models/actor/actor'
8import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 8import { ActorFollowModel } from '../../../models/actor/actor-follow'
9import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' 9import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
10import { VideoShareModel } from '../../../models/video/video-share' 10import { VideoShareModel } from '../../../models/video/video-share'
11import { APProcessorOptions } from '../../../types/activitypub-processor.model' 11import { APProcessorOptions } from '../../../types/activitypub-processor.model'
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index 6df9b93b2..6cd9d0fba 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -1,23 +1,23 @@
1import { isRedundancyAccepted } from '@server/lib/redundancy'
2import { ActorImageType } from '@shared/models'
1import { ActivityUpdate, CacheFileObject, VideoObject } from '../../../../shared/models/activitypub' 3import { ActivityUpdate, CacheFileObject, VideoObject } from '../../../../shared/models/activitypub'
2import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor' 4import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor'
5import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
6import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file'
7import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos'
3import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' 8import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils'
4import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
5import { sequelizeTypescript } from '../../../initializers/database' 10import { sequelizeTypescript } from '../../../initializers/database'
6import { AccountModel } from '../../../models/account/account' 11import { AccountModel } from '../../../models/account/account'
7import { ActorModel } from '../../../models/activitypub/actor' 12import { ActorModel } from '../../../models/actor/actor'
8import { VideoChannelModel } from '../../../models/video/video-channel' 13import { VideoChannelModel } from '../../../models/video/video-channel'
14import { APProcessorOptions } from '../../../types/activitypub-processor.model'
15import { MAccountIdActor, MActorSignature } from '../../../types/models'
9import { getImageInfoIfExists, updateActorImageInstance, updateActorInstance } from '../actor' 16import { getImageInfoIfExists, updateActorImageInstance, updateActorInstance } from '../actor'
10import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, updateVideoFromAP } from '../videos'
11import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos'
12import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file'
13import { createOrUpdateCacheFile } from '../cache-file' 17import { createOrUpdateCacheFile } from '../cache-file'
14import { forwardVideoRelatedActivity } from '../send/utils'
15import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
16import { createOrUpdateVideoPlaylist } from '../playlist' 18import { createOrUpdateVideoPlaylist } from '../playlist'
17import { APProcessorOptions } from '../../../types/activitypub-processor.model' 19import { forwardVideoRelatedActivity } from '../send/utils'
18import { MActorSignature, MAccountIdActor } from '../../../types/models' 20import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, updateVideoFromAP } from '../videos'
19import { isRedundancyAccepted } from '@server/lib/redundancy'
20import { ActorImageType } from '@shared/models'
21 21
22async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { 22async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) {
23 const { activity, byActor } = options 23 const { activity, byActor } = options
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts
index e0acced18..d31f8c10b 100644
--- a/server/lib/activitypub/send/send-delete.ts
+++ b/server/lib/activitypub/send/send-delete.ts
@@ -2,7 +2,7 @@ import { Transaction } from 'sequelize'
2import { getServerActor } from '@server/models/application/application' 2import { getServerActor } from '@server/models/application/application'
3import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub' 3import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub'
4import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
5import { ActorModel } from '../../../models/activitypub/actor' 5import { ActorModel } from '../../../models/actor/actor'
6import { VideoCommentModel } from '../../../models/video/video-comment' 6import { VideoCommentModel } from '../../../models/video/video-comment'
7import { VideoShareModel } from '../../../models/video/video-share' 7import { VideoShareModel } from '../../../models/video/video-share'
8import { MActorUrl } from '../../../types/models' 8import { MActorUrl } from '../../../types/models'
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts
index 9254dc7c5..153e94295 100644
--- a/server/lib/activitypub/send/send-view.ts
+++ b/server/lib/activitypub/send/send-view.ts
@@ -2,7 +2,7 @@ import { Transaction } from 'sequelize'
2import { MActorAudience, MVideoImmutable, MVideoUrl } from '@server/types/models' 2import { MActorAudience, MVideoImmutable, MVideoUrl } from '@server/types/models'
3import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub' 3import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub'
4import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
5import { ActorModel } from '../../../models/activitypub/actor' 5import { ActorModel } from '../../../models/actor/actor'
6import { audiencify, getAudience } from '../audience' 6import { audiencify, getAudience } from '../audience'
7import { getLocalVideoViewActivityPubUrl } from '../url' 7import { getLocalVideoViewActivityPubUrl } from '../url'
8import { sendVideoRelatedActivity } from './utils' 8import { sendVideoRelatedActivity } from './utils'
diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts
index 85a9f009d..db0e91b71 100644
--- a/server/lib/activitypub/send/utils.ts
+++ b/server/lib/activitypub/send/utils.ts
@@ -1,14 +1,14 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { getServerActor } from '@server/models/application/application'
3import { ContextType } from '@shared/models/activitypub/context'
2import { Activity, ActivityAudience } from '../../../../shared/models/activitypub' 4import { Activity, ActivityAudience } from '../../../../shared/models/activitypub'
5import { afterCommitIfTransaction } from '../../../helpers/database-utils'
3import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
4import { ActorModel } from '../../../models/activitypub/actor' 7import { ActorModel } from '../../../models/actor/actor'
5import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 8import { ActorFollowModel } from '../../../models/actor/actor-follow'
9import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../types/models'
6import { JobQueue } from '../../job-queue' 10import { JobQueue } from '../../job-queue'
7import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' 11import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience'
8import { afterCommitIfTransaction } from '../../../helpers/database-utils'
9import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../types/models'
10import { getServerActor } from '@server/models/application/application'
11import { ContextType } from '@shared/models/activitypub/context'
12 12
13async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { 13async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
14 byActor: MActorLight 14 byActor: MActorLight
diff --git a/server/lib/auth/oauth-model.ts b/server/lib/auth/oauth-model.ts
index b9c69eb2d..ae728d080 100644
--- a/server/lib/auth/oauth-model.ts
+++ b/server/lib/auth/oauth-model.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { AccessDeniedError } from 'oauth2-server' 2import { AccessDeniedError } from 'oauth2-server'
3import { PluginManager } from '@server/lib/plugins/plugin-manager' 3import { PluginManager } from '@server/lib/plugins/plugin-manager'
4import { ActorModel } from '@server/models/activitypub/actor' 4import { ActorModel } from '@server/models/actor/actor'
5import { MOAuthClient } from '@server/types/models' 5import { MOAuthClient } from '@server/types/models'
6import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token' 6import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token'
7import { MUser } from '@server/types/models/user/user' 7import { MUser } from '@server/types/models/user/user'
@@ -9,7 +9,7 @@ import { UserAdminFlag } from '@shared/models/users/user-flag.model'
9import { UserRole } from '@shared/models/users/user-role' 9import { UserRole } from '@shared/models/users/user-role'
10import { logger } from '../../helpers/logger' 10import { logger } from '../../helpers/logger'
11import { CONFIG } from '../../initializers/config' 11import { CONFIG } from '../../initializers/config'
12import { UserModel } from '../../models/account/user' 12import { UserModel } from '../../models/user/user'
13import { OAuthClientModel } from '../../models/oauth/oauth-client' 13import { OAuthClientModel } from '../../models/oauth/oauth-client'
14import { OAuthTokenModel } from '../../models/oauth/oauth-token' 14import { OAuthTokenModel } from '../../models/oauth/oauth-token'
15import { createUserAccountAndChannelAndPlaylist } from '../user' 15import { createUserAccountAndChannelAndPlaylist } from '../user'
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index 203bd3893..85fdc8754 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -2,12 +2,14 @@ import * as express from 'express'
2import { readFile } from 'fs-extra' 2import { readFile } from 'fs-extra'
3import { join } from 'path' 3import { join } from 'path'
4import validator from 'validator' 4import validator from 'validator'
5import { escapeHTML } from '@shared/core-utils/renderer'
6import { HTMLServerConfig } from '@shared/models'
5import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n' 7import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n'
6import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' 8import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
7import { VideoPlaylistPrivacy, VideoPrivacy } from '../../shared/models/videos' 9import { VideoPlaylistPrivacy, VideoPrivacy } from '../../shared/models/videos'
8import { isTestInstance, sha256 } from '../helpers/core-utils' 10import { isTestInstance, sha256 } from '../helpers/core-utils'
9import { escapeHTML } from '@shared/core-utils/renderer'
10import { logger } from '../helpers/logger' 11import { logger } from '../helpers/logger'
12import { mdToPlainText } from '../helpers/markdown'
11import { CONFIG } from '../initializers/config' 13import { CONFIG } from '../initializers/config'
12import { 14import {
13 ACCEPT_HEADERS, 15 ACCEPT_HEADERS,
@@ -24,7 +26,7 @@ import { VideoChannelModel } from '../models/video/video-channel'
24import { getActivityStreamDuration } from '../models/video/video-format-utils' 26import { getActivityStreamDuration } from '../models/video/video-format-utils'
25import { VideoPlaylistModel } from '../models/video/video-playlist' 27import { VideoPlaylistModel } from '../models/video/video-playlist'
26import { MAccountActor, MChannelActor } from '../types/models' 28import { MAccountActor, MChannelActor } from '../types/models'
27import { mdToPlainText } from '../helpers/markdown' 29import { getHTMLServerConfig } from './config'
28 30
29type Tags = { 31type Tags = {
30 ogType: string 32 ogType: string
@@ -209,11 +211,14 @@ class ClientHtml {
209 if (!isTestInstance() && ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] 211 if (!isTestInstance() && ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path]
210 212
211 const buffer = await readFile(path) 213 const buffer = await readFile(path)
214 const serverConfig = await getHTMLServerConfig()
212 215
213 let html = buffer.toString() 216 let html = buffer.toString()
214 html = await ClientHtml.addAsyncPluginCSS(html) 217 html = await ClientHtml.addAsyncPluginCSS(html)
215 html = ClientHtml.addCustomCSS(html) 218 html = ClientHtml.addCustomCSS(html)
216 html = ClientHtml.addTitleTag(html) 219 html = ClientHtml.addTitleTag(html)
220 html = ClientHtml.addDescriptionTag(html)
221 html = ClientHtml.addServerConfig(html, serverConfig)
217 222
218 ClientHtml.htmlCache[path] = html 223 ClientHtml.htmlCache[path] = html
219 224
@@ -275,6 +280,7 @@ class ClientHtml {
275 if (!isTestInstance() && ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] 280 if (!isTestInstance() && ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path]
276 281
277 const buffer = await readFile(path) 282 const buffer = await readFile(path)
283 const serverConfig = await getHTMLServerConfig()
278 284
279 let html = buffer.toString() 285 let html = buffer.toString()
280 286
@@ -283,6 +289,7 @@ class ClientHtml {
283 html = ClientHtml.addFaviconContentHash(html) 289 html = ClientHtml.addFaviconContentHash(html)
284 html = ClientHtml.addLogoContentHash(html) 290 html = ClientHtml.addLogoContentHash(html)
285 html = ClientHtml.addCustomCSS(html) 291 html = ClientHtml.addCustomCSS(html)
292 html = ClientHtml.addServerConfig(html, serverConfig)
286 html = await ClientHtml.addAsyncPluginCSS(html) 293 html = await ClientHtml.addAsyncPluginCSS(html)
287 294
288 ClientHtml.htmlCache[path] = html 295 ClientHtml.htmlCache[path] = html
@@ -355,6 +362,13 @@ class ClientHtml {
355 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.CUSTOM_CSS, styleTag) 362 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.CUSTOM_CSS, styleTag)
356 } 363 }
357 364
365 private static addServerConfig (htmlStringPage: string, serverConfig: HTMLServerConfig) {
366 const serverConfigString = JSON.stringify(serverConfig)
367 const configScriptTag = `<script type="application/javascript">window.PeerTubeServerConfig = '${serverConfigString}'</script>`
368
369 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.SERVER_CONFIG, configScriptTag)
370 }
371
358 private static async addAsyncPluginCSS (htmlStringPage: string) { 372 private static async addAsyncPluginCSS (htmlStringPage: string) {
359 const globalCSSContent = await readFile(PLUGIN_GLOBAL_CSS_PATH) 373 const globalCSSContent = await readFile(PLUGIN_GLOBAL_CSS_PATH)
360 if (globalCSSContent.byteLength === 0) return htmlStringPage 374 if (globalCSSContent.byteLength === 0) return htmlStringPage
diff --git a/server/lib/config.ts b/server/lib/config.ts
index b4c4c9299..18d49f05a 100644
--- a/server/lib/config.ts
+++ b/server/lib/config.ts
@@ -2,18 +2,13 @@ import { isSignupAllowed, isSignupAllowedForCurrentIP } from '@server/helpers/si
2import { getServerCommit } from '@server/helpers/utils' 2import { getServerCommit } from '@server/helpers/utils'
3import { CONFIG, isEmailEnabled } from '@server/initializers/config' 3import { CONFIG, isEmailEnabled } from '@server/initializers/config'
4import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '@server/initializers/constants' 4import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '@server/initializers/constants'
5import { RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig } from '@shared/models' 5import { HTMLServerConfig, RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig } from '@shared/models'
6import { Hooks } from './plugins/hooks' 6import { Hooks } from './plugins/hooks'
7import { PluginManager } from './plugins/plugin-manager' 7import { PluginManager } from './plugins/plugin-manager'
8import { getThemeOrDefault } from './plugins/theme-utils' 8import { getThemeOrDefault } from './plugins/theme-utils'
9import { getEnabledResolutions } from './video-transcoding' 9import { VideoTranscodingProfilesManager } from './transcoding/video-transcoding-profiles'
10import { VideoTranscodingProfilesManager } from './video-transcoding-profiles'
11
12let serverCommit: string
13 10
14async function getServerConfig (ip?: string): Promise<ServerConfig> { 11async function getServerConfig (ip?: string): Promise<ServerConfig> {
15 if (serverCommit === undefined) serverCommit = await getServerCommit()
16
17 const { allowed } = await Hooks.wrapPromiseFun( 12 const { allowed } = await Hooks.wrapPromiseFun(
18 isSignupAllowed, 13 isSignupAllowed,
19 { 14 {
@@ -23,6 +18,23 @@ async function getServerConfig (ip?: string): Promise<ServerConfig> {
23 ) 18 )
24 19
25 const allowedForCurrentIP = isSignupAllowedForCurrentIP(ip) 20 const allowedForCurrentIP = isSignupAllowedForCurrentIP(ip)
21
22 const signup = {
23 allowed,
24 allowedForCurrentIP,
25 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
26 }
27
28 const htmlConfig = await getHTMLServerConfig()
29
30 return { ...htmlConfig, signup }
31}
32
33// Config injected in HTML
34let serverCommit: string
35async function getHTMLServerConfig (): Promise<HTMLServerConfig> {
36 if (serverCommit === undefined) serverCommit = await getServerCommit()
37
26 const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME) 38 const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
27 39
28 return { 40 return {
@@ -66,11 +78,6 @@ async function getServerConfig (ip?: string): Promise<ServerConfig> {
66 }, 78 },
67 serverVersion: PEERTUBE_VERSION, 79 serverVersion: PEERTUBE_VERSION,
68 serverCommit, 80 serverCommit,
69 signup: {
70 allowed,
71 allowedForCurrentIP,
72 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
73 },
74 transcoding: { 81 transcoding: {
75 hls: { 82 hls: {
76 enabled: CONFIG.TRANSCODING.HLS.ENABLED 83 enabled: CONFIG.TRANSCODING.HLS.ENABLED
@@ -208,12 +215,24 @@ function getRegisteredPlugins () {
208 })) 215 }))
209} 216}
210 217
218function getEnabledResolutions (type: 'vod' | 'live') {
219 const transcoding = type === 'vod'
220 ? CONFIG.TRANSCODING
221 : CONFIG.LIVE.TRANSCODING
222
223 return Object.keys(transcoding.RESOLUTIONS)
224 .filter(key => transcoding.ENABLED && transcoding.RESOLUTIONS[key] === true)
225 .map(r => parseInt(r, 10))
226}
227
211// --------------------------------------------------------------------------- 228// ---------------------------------------------------------------------------
212 229
213export { 230export {
214 getServerConfig, 231 getServerConfig,
215 getRegisteredThemes, 232 getRegisteredThemes,
216 getRegisteredPlugins 233 getEnabledResolutions,
234 getRegisteredPlugins,
235 getHTMLServerConfig
217} 236}
218 237
219// --------------------------------------------------------------------------- 238// ---------------------------------------------------------------------------
diff --git a/server/lib/job-queue/handlers/activitypub-follow.ts b/server/lib/job-queue/handlers/activitypub-follow.ts
index 82c95be80..ec8df8969 100644
--- a/server/lib/job-queue/handlers/activitypub-follow.ts
+++ b/server/lib/job-queue/handlers/activitypub-follow.ts
@@ -1,18 +1,18 @@
1import * as Bull from 'bull' 1import * as Bull from 'bull'
2import { logger } from '../../../helpers/logger' 2import { getLocalActorFollowActivityPubUrl } from '@server/lib/activitypub/url'
3import { REMOTE_SCHEME, WEBSERVER } from '../../../initializers/constants' 3import { ActivitypubFollowPayload } from '@shared/models'
4import { sendFollow } from '../../activitypub/send'
5import { sanitizeHost } from '../../../helpers/core-utils' 4import { sanitizeHost } from '../../../helpers/core-utils'
6import { loadActorUrlOrGetFromWebfinger } from '../../../helpers/webfinger'
7import { getOrCreateActorAndServerAndModel } from '../../activitypub/actor'
8import { retryTransactionWrapper } from '../../../helpers/database-utils' 5import { retryTransactionWrapper } from '../../../helpers/database-utils'
9import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 6import { logger } from '../../../helpers/logger'
10import { ActorModel } from '../../../models/activitypub/actor' 7import { loadActorUrlOrGetFromWebfinger } from '../../../helpers/webfinger'
11import { Notifier } from '../../notifier' 8import { REMOTE_SCHEME, WEBSERVER } from '../../../initializers/constants'
12import { sequelizeTypescript } from '../../../initializers/database' 9import { sequelizeTypescript } from '../../../initializers/database'
10import { ActorModel } from '../../../models/actor/actor'
11import { ActorFollowModel } from '../../../models/actor/actor-follow'
13import { MActor, MActorFollowActors, MActorFull } from '../../../types/models' 12import { MActor, MActorFollowActors, MActorFull } from '../../../types/models'
14import { ActivitypubFollowPayload } from '@shared/models' 13import { getOrCreateActorAndServerAndModel } from '../../activitypub/actor'
15import { getLocalActorFollowActivityPubUrl } from '@server/lib/activitypub/url' 14import { sendFollow } from '../../activitypub/send'
15import { Notifier } from '../../notifier'
16 16
17async function processActivityPubFollow (job: Bull.Job) { 17async function processActivityPubFollow (job: Bull.Job) {
18 const payload = job.data as ActivitypubFollowPayload 18 const payload = job.data as ActivitypubFollowPayload
diff --git a/server/lib/job-queue/handlers/activitypub-refresher.ts b/server/lib/job-queue/handlers/activitypub-refresher.ts
index 666e56868..c09b1bcc8 100644
--- a/server/lib/job-queue/handlers/activitypub-refresher.ts
+++ b/server/lib/job-queue/handlers/activitypub-refresher.ts
@@ -1,12 +1,12 @@
1import * as Bull from 'bull' 1import * as Bull from 'bull'
2import { refreshVideoPlaylistIfNeeded } from '@server/lib/activitypub/playlist'
3import { RefreshPayload } from '@shared/models'
2import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
3import { fetchVideoByUrl } from '../../../helpers/video' 5import { fetchVideoByUrl } from '../../../helpers/video'
6import { ActorModel } from '../../../models/actor/actor'
7import { VideoPlaylistModel } from '../../../models/video/video-playlist'
4import { refreshActorIfNeeded } from '../../activitypub/actor' 8import { refreshActorIfNeeded } from '../../activitypub/actor'
5import { refreshVideoIfNeeded } from '../../activitypub/videos' 9import { refreshVideoIfNeeded } from '../../activitypub/videos'
6import { ActorModel } from '../../../models/activitypub/actor'
7import { VideoPlaylistModel } from '../../../models/video/video-playlist'
8import { RefreshPayload } from '@shared/models'
9import { refreshVideoPlaylistIfNeeded } from '@server/lib/activitypub/playlist'
10 10
11async function refreshAPObject (job: Bull.Job) { 11async function refreshAPObject (job: Bull.Job) {
12 const payload = job.data as RefreshPayload 12 const payload = job.data as RefreshPayload
diff --git a/server/lib/job-queue/handlers/actor-keys.ts b/server/lib/job-queue/handlers/actor-keys.ts
index 125307843..3eef565d0 100644
--- a/server/lib/job-queue/handlers/actor-keys.ts
+++ b/server/lib/job-queue/handlers/actor-keys.ts
@@ -1,6 +1,6 @@
1import * as Bull from 'bull' 1import * as Bull from 'bull'
2import { generateAndSaveActorKeys } from '@server/lib/activitypub/actor' 2import { generateAndSaveActorKeys } from '@server/lib/activitypub/actor'
3import { ActorModel } from '@server/models/activitypub/actor' 3import { ActorModel } from '@server/models/actor/actor'
4import { ActorKeysPayload } from '@shared/models' 4import { ActorKeysPayload } from '@shared/models'
5import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6 6
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 e8a91450d..37e7c1fad 100644
--- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
+++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
@@ -1,10 +1,10 @@
1import { buildDigest } from '@server/helpers/peertube-crypto'
2import { getServerActor } from '@server/models/application/application'
3import { ContextType } from '@shared/models/activitypub/context'
1import { buildSignedActivity } from '../../../../helpers/activitypub' 4import { buildSignedActivity } from '../../../../helpers/activitypub'
2import { ActorModel } from '../../../../models/activitypub/actor'
3import { ACTIVITY_PUB, HTTP_SIGNATURE } from '../../../../initializers/constants' 5import { ACTIVITY_PUB, HTTP_SIGNATURE } from '../../../../initializers/constants'
6import { ActorModel } from '../../../../models/actor/actor'
4import { MActor } from '../../../../types/models' 7import { MActor } from '../../../../types/models'
5import { getServerActor } from '@server/models/application/application'
6import { buildDigest } from '@server/helpers/peertube-crypto'
7import { ContextType } from '@shared/models/activitypub/context'
8 8
9type Payload <T> = { body: T, contextType?: ContextType, signatureActorId?: number } 9type Payload <T> = { body: T, contextType?: ContextType, signatureActorId?: number }
10 10
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts
index 71f2cafcd..8297a1571 100644
--- a/server/lib/job-queue/handlers/video-file-import.ts
+++ b/server/lib/job-queue/handlers/video-file-import.ts
@@ -3,7 +3,7 @@ import { copy, stat } from 'fs-extra'
3import { extname } from 'path' 3import { extname } from 'path'
4import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 4import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
5import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 5import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
6import { UserModel } from '@server/models/account/user' 6import { UserModel } from '@server/models/user/user'
7import { MVideoFullLight } from '@server/types/models' 7import { MVideoFullLight } from '@server/types/models'
8import { VideoFileImportPayload } from '@shared/models' 8import { VideoFileImportPayload } from '@shared/models'
9import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' 9import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils'
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index ed2c5eac0..3067ce214 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -23,7 +23,6 @@ import { getDurationFromVideoFile, getVideoFileFPS, getVideoFileResolution } fro
23import { logger } from '../../../helpers/logger' 23import { logger } from '../../../helpers/logger'
24import { getSecureTorrentName } from '../../../helpers/utils' 24import { getSecureTorrentName } from '../../../helpers/utils'
25import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent' 25import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent'
26import { downloadYoutubeDLVideo } from '../../../helpers/youtube-dl'
27import { CONFIG } from '../../../initializers/config' 26import { CONFIG } from '../../../initializers/config'
28import { VIDEO_IMPORT_TIMEOUT } from '../../../initializers/constants' 27import { VIDEO_IMPORT_TIMEOUT } from '../../../initializers/constants'
29import { sequelizeTypescript } from '../../../initializers/database' 28import { sequelizeTypescript } from '../../../initializers/database'
@@ -34,6 +33,8 @@ import { MThumbnail } from '../../../types/models/video/thumbnail'
34import { federateVideoIfNeeded } from '../../activitypub/videos' 33import { federateVideoIfNeeded } from '../../activitypub/videos'
35import { Notifier } from '../../notifier' 34import { Notifier } from '../../notifier'
36import { generateVideoMiniature } from '../../thumbnail' 35import { generateVideoMiniature } from '../../thumbnail'
36import { YoutubeDL } from '@server/helpers/youtube-dl'
37import { getEnabledResolutions } from '@server/lib/config'
37 38
38async function processVideoImport (job: Bull.Job) { 39async function processVideoImport (job: Bull.Job) {
39 const payload = job.data as VideoImportPayload 40 const payload = job.data as VideoImportPayload
@@ -75,8 +76,10 @@ async function processYoutubeDLImport (job: Bull.Job, payload: VideoImportYoutub
75 videoImportId: videoImport.id 76 videoImportId: videoImport.id
76 } 77 }
77 78
79 const youtubeDL = new YoutubeDL(videoImport.targetUrl, getEnabledResolutions('vod'))
80
78 return processFile( 81 return processFile(
79 () => downloadYoutubeDLVideo(videoImport.targetUrl, payload.fileExt, VIDEO_IMPORT_TIMEOUT), 82 () => youtubeDL.downloadYoutubeDLVideo(payload.fileExt, VIDEO_IMPORT_TIMEOUT),
80 videoImport, 83 videoImport,
81 options 84 options
82 ) 85 )
diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts
index d57202ca5..517b90abc 100644
--- a/server/lib/job-queue/handlers/video-live-ending.ts
+++ b/server/lib/job-queue/handlers/video-live-ending.ts
@@ -5,9 +5,9 @@ import { ffprobePromise, getAudioStream, getDurationFromVideoFile, getVideoFileR
5import { VIDEO_LIVE } from '@server/initializers/constants' 5import { VIDEO_LIVE } from '@server/initializers/constants'
6import { LiveManager } from '@server/lib/live-manager' 6import { LiveManager } from '@server/lib/live-manager'
7import { generateVideoMiniature } from '@server/lib/thumbnail' 7import { generateVideoMiniature } from '@server/lib/thumbnail'
8import { generateHlsPlaylistResolutionFromTS } from '@server/lib/transcoding/video-transcoding'
8import { publishAndFederateIfNeeded } from '@server/lib/video' 9import { publishAndFederateIfNeeded } from '@server/lib/video'
9import { getHLSDirectory } from '@server/lib/video-paths' 10import { getHLSDirectory } from '@server/lib/video-paths'
10import { generateHlsPlaylistResolutionFromTS } from '@server/lib/video-transcoding'
11import { VideoModel } from '@server/models/video/video' 11import { VideoModel } from '@server/models/video/video'
12import { VideoFileModel } from '@server/models/video/video-file' 12import { VideoFileModel } from '@server/models/video/video-file'
13import { VideoLiveModel } from '@server/models/video/video-live' 13import { VideoLiveModel } from '@server/models/video/video-live'
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts
index 010b95b05..8d659daa6 100644
--- a/server/lib/job-queue/handlers/video-transcoding.ts
+++ b/server/lib/job-queue/handlers/video-transcoding.ts
@@ -2,7 +2,7 @@ import * as Bull from 'bull'
2import { TranscodeOptionsType } from '@server/helpers/ffmpeg-utils' 2import { TranscodeOptionsType } from '@server/helpers/ffmpeg-utils'
3import { getTranscodingJobPriority, publishAndFederateIfNeeded } from '@server/lib/video' 3import { getTranscodingJobPriority, publishAndFederateIfNeeded } from '@server/lib/video'
4import { getVideoFilePath } from '@server/lib/video-paths' 4import { getVideoFilePath } from '@server/lib/video-paths'
5import { UserModel } from '@server/models/account/user' 5import { UserModel } from '@server/models/user/user'
6import { MUser, MUserId, MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/types/models' 6import { MUser, MUserId, MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/types/models'
7import { 7import {
8 HLSTranscodingPayload, 8 HLSTranscodingPayload,
@@ -24,7 +24,7 @@ import {
24 mergeAudioVideofile, 24 mergeAudioVideofile,
25 optimizeOriginalVideofile, 25 optimizeOriginalVideofile,
26 transcodeNewWebTorrentResolution 26 transcodeNewWebTorrentResolution
27} from '../../video-transcoding' 27} from '../../transcoding/video-transcoding'
28import { JobQueue } from '../job-queue' 28import { JobQueue } from '../job-queue'
29 29
30type HandlerFunction = (job: Bull.Job, payload: VideoTranscodingPayload, video: MVideoFullLight, user: MUser) => Promise<any> 30type HandlerFunction = (job: Bull.Job, payload: VideoTranscodingPayload, video: MVideoFullLight, user: MUser) => Promise<any>
diff --git a/server/lib/job-queue/handlers/video-views.ts b/server/lib/job-queue/handlers/video-views.ts
index 897235ec0..86d0a271f 100644
--- a/server/lib/job-queue/handlers/video-views.ts
+++ b/server/lib/job-queue/handlers/video-views.ts
@@ -36,8 +36,8 @@ async function processVideosViews () {
36 } 36 }
37 37
38 await VideoViewModel.create({ 38 await VideoViewModel.create({
39 startDate, 39 startDate: new Date(startDate),
40 endDate, 40 endDate: new Date(endDate),
41 views, 41 views,
42 videoId 42 videoId
43 }) 43 })
diff --git a/server/lib/live-manager.ts b/server/lib/live-manager.ts
index 66b5d119b..8e7fd5511 100644
--- a/server/lib/live-manager.ts
+++ b/server/lib/live-manager.ts
@@ -11,7 +11,7 @@ import { computeResolutionsToTranscode, getVideoFileFPS, getVideoFileResolution
11import { logger } from '@server/helpers/logger' 11import { logger } from '@server/helpers/logger'
12import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config' 12import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config'
13import { MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE, VIEW_LIFETIME, WEBSERVER } from '@server/initializers/constants' 13import { MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE, VIEW_LIFETIME, WEBSERVER } from '@server/initializers/constants'
14import { UserModel } from '@server/models/account/user' 14import { UserModel } from '@server/models/user/user'
15import { VideoModel } from '@server/models/video/video' 15import { VideoModel } from '@server/models/video/video'
16import { VideoFileModel } from '@server/models/video/video-file' 16import { VideoFileModel } from '@server/models/video/video-file'
17import { VideoLiveModel } from '@server/models/video/video-live' 17import { VideoLiveModel } from '@server/models/video/video-live'
@@ -23,9 +23,9 @@ import { buildSha256Segment } from './hls'
23import { JobQueue } from './job-queue' 23import { JobQueue } from './job-queue'
24import { cleanupLive } from './job-queue/handlers/video-live-ending' 24import { cleanupLive } from './job-queue/handlers/video-live-ending'
25import { PeerTubeSocket } from './peertube-socket' 25import { PeerTubeSocket } from './peertube-socket'
26import { VideoTranscodingProfilesManager } from './transcoding/video-transcoding-profiles'
26import { isAbleToUploadVideo } from './user' 27import { isAbleToUploadVideo } from './user'
27import { getHLSDirectory } from './video-paths' 28import { getHLSDirectory } from './video-paths'
28import { VideoTranscodingProfilesManager } from './video-transcoding-profiles'
29 29
30import memoizee = require('memoizee') 30import memoizee = require('memoizee')
31const NodeRtmpSession = require('node-media-server/node_rtmp_session') 31const NodeRtmpSession = require('node-media-server/node_rtmp_session')
diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts
index 925d64902..0cefe1648 100644
--- a/server/lib/moderation.ts
+++ b/server/lib/moderation.ts
@@ -23,9 +23,9 @@ import { ActivityCreate } from '../../shared/models/activitypub'
23import { VideoObject } from '../../shared/models/activitypub/objects' 23import { VideoObject } from '../../shared/models/activitypub/objects'
24import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object' 24import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object'
25import { LiveVideoCreate, VideoCreate, VideoImportCreate } from '../../shared/models/videos' 25import { LiveVideoCreate, VideoCreate, VideoImportCreate } from '../../shared/models/videos'
26import { VideoCommentCreate } from '../../shared/models/videos/video-comment.model' 26import { VideoCommentCreate } from '../../shared/models/videos/comment/video-comment.model'
27import { UserModel } from '../models/account/user' 27import { ActorModel } from '../models/actor/actor'
28import { ActorModel } from '../models/activitypub/actor' 28import { UserModel } from '../models/user/user'
29import { VideoModel } from '../models/video/video' 29import { VideoModel } from '../models/video/video'
30import { VideoCommentModel } from '../models/video/video-comment' 30import { VideoCommentModel } from '../models/video/video-comment'
31import { sendAbuse } from './activitypub/send/send-flag' 31import { sendAbuse } from './activitypub/send/send-flag'
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts
index da7f7cc05..1f9ff16df 100644
--- a/server/lib/notifier.ts
+++ b/server/lib/notifier.ts
@@ -17,8 +17,8 @@ import { VideoPrivacy, VideoState } from '../../shared/models/videos'
17import { logger } from '../helpers/logger' 17import { logger } from '../helpers/logger'
18import { CONFIG } from '../initializers/config' 18import { CONFIG } from '../initializers/config'
19import { AccountBlocklistModel } from '../models/account/account-blocklist' 19import { AccountBlocklistModel } from '../models/account/account-blocklist'
20import { UserModel } from '../models/account/user' 20import { UserModel } from '../models/user/user'
21import { UserNotificationModel } from '../models/account/user-notification' 21import { UserNotificationModel } from '../models/user/user-notification'
22import { MAbuseFull, MAbuseMessage, MAccountServer, MActorFollowFull, MApplication, MPlugin } from '../types/models' 22import { MAbuseFull, MAbuseMessage, MAccountServer, MActorFollowFull, MApplication, MPlugin } from '../types/models'
23import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video' 23import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video'
24import { isBlockedByServerOrAccount } from './blocklist' 24import { isBlockedByServerOrAccount } from './blocklist'
diff --git a/server/lib/plugins/hooks.ts b/server/lib/plugins/hooks.ts
index aa92f03cc..5e97b52a0 100644
--- a/server/lib/plugins/hooks.ts
+++ b/server/lib/plugins/hooks.ts
@@ -1,7 +1,7 @@
1import { ServerActionHookName, ServerFilterHookName } from '../../../shared/models/plugins/server-hook.model'
2import { PluginManager } from './plugin-manager'
3import { logger } from '../../helpers/logger'
4import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { ServerActionHookName, ServerFilterHookName } from '../../../shared/models'
3import { logger } from '../../helpers/logger'
4import { PluginManager } from './plugin-manager'
5 5
6type PromiseFunction <U, T> = (params: U) => Promise<T> | Bluebird<T> 6type PromiseFunction <U, T> = (params: U) => Promise<T> | Bluebird<T>
7type RawFunction <U, T> = (params: U) => T 7type RawFunction <U, T> = (params: U) => T
diff --git a/server/lib/plugins/plugin-helpers-builder.ts b/server/lib/plugins/plugin-helpers-builder.ts
index f1bc24d8b..cb1cd4d9a 100644
--- a/server/lib/plugins/plugin-helpers-builder.ts
+++ b/server/lib/plugins/plugin-helpers-builder.ts
@@ -17,7 +17,7 @@ import { VideoBlacklistCreate } from '@shared/models'
17import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../blocklist' 17import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../blocklist'
18import { getServerConfig } from '../config' 18import { getServerConfig } from '../config'
19import { blacklistVideo, unblacklistVideo } from '../video-blacklist' 19import { blacklistVideo, unblacklistVideo } from '../video-blacklist'
20import { UserModel } from '@server/models/account/user' 20import { UserModel } from '@server/models/user/user'
21 21
22function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHelpers { 22function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHelpers {
23 const logger = buildPluginLogger(npmName) 23 const logger = buildPluginLogger(npmName)
diff --git a/server/lib/plugins/plugin-index.ts b/server/lib/plugins/plugin-index.ts
index 165bc91b3..119cee8e0 100644
--- a/server/lib/plugins/plugin-index.ts
+++ b/server/lib/plugins/plugin-index.ts
@@ -1,16 +1,16 @@
1import { sanitizeUrl } from '@server/helpers/core-utils' 1import { sanitizeUrl } from '@server/helpers/core-utils'
2import { ResultList } from '../../../shared/models' 2import { logger } from '@server/helpers/logger'
3import { PeertubePluginIndexList } from '../../../shared/models/plugins/peertube-plugin-index-list.model' 3import { doJSONRequest } from '@server/helpers/requests'
4import { PeerTubePluginIndex } from '../../../shared/models/plugins/peertube-plugin-index.model' 4import { CONFIG } from '@server/initializers/config'
5import { PEERTUBE_VERSION } from '@server/initializers/constants'
6import { PluginModel } from '@server/models/server/plugin'
5import { 7import {
8 PeerTubePluginIndex,
9 PeertubePluginIndexList,
6 PeertubePluginLatestVersionRequest, 10 PeertubePluginLatestVersionRequest,
7 PeertubePluginLatestVersionResponse 11 PeertubePluginLatestVersionResponse,
8} from '../../../shared/models/plugins/peertube-plugin-latest-version.model' 12 ResultList
9import { logger } from '../../helpers/logger' 13} from '@shared/models'
10import { doJSONRequest } from '../../helpers/requests'
11import { CONFIG } from '../../initializers/config'
12import { PEERTUBE_VERSION } from '../../initializers/constants'
13import { PluginModel } from '../../models/server/plugin'
14import { PluginManager } from './plugin-manager' 14import { PluginManager } from './plugin-manager'
15 15
16async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) { 16async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) {
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts
index ba9814383..6b9a255a4 100644
--- a/server/lib/plugins/plugin-manager.ts
+++ b/server/lib/plugins/plugin-manager.ts
@@ -4,16 +4,11 @@ import { createReadStream, createWriteStream } from 'fs'
4import { ensureDir, outputFile, readJSON } from 'fs-extra' 4import { ensureDir, outputFile, readJSON } from 'fs-extra'
5import { basename, join } from 'path' 5import { basename, join } from 'path'
6import { MOAuthTokenUser, MUser } from '@server/types/models' 6import { MOAuthTokenUser, MUser } from '@server/types/models'
7import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model' 7import { getCompleteLocale } from '@shared/core-utils'
8import { ClientScript, PluginPackageJson, PluginTranslation, PluginTranslationPaths, RegisterServerHookOptions } from '@shared/models'
8import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks' 9import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks'
9import {
10 ClientScript,
11 PluginPackageJson,
12 PluginTranslationPaths as PackagePluginTranslations
13} from '../../../shared/models/plugins/plugin-package-json.model'
14import { PluginTranslation } from '../../../shared/models/plugins/plugin-translation.model'
15import { PluginType } from '../../../shared/models/plugins/plugin.type' 10import { PluginType } from '../../../shared/models/plugins/plugin.type'
16import { ServerHook, ServerHookName } from '../../../shared/models/plugins/server-hook.model' 11import { ServerHook, ServerHookName } from '../../../shared/models/plugins/server/server-hook.model'
17import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins' 12import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins'
18import { logger } from '../../helpers/logger' 13import { logger } from '../../helpers/logger'
19import { CONFIG } from '../../initializers/config' 14import { CONFIG } from '../../initializers/config'
@@ -23,7 +18,6 @@ import { PluginLibrary, RegisterServerAuthExternalOptions, RegisterServerAuthPas
23import { ClientHtml } from '../client-html' 18import { ClientHtml } from '../client-html'
24import { RegisterHelpers } from './register-helpers' 19import { RegisterHelpers } from './register-helpers'
25import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' 20import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn'
26import { getCompleteLocale } from '@shared/core-utils'
27 21
28export interface RegisteredPlugin { 22export interface RegisteredPlugin {
29 npmName: string 23 npmName: string
@@ -443,7 +437,7 @@ export class PluginManager implements ServerHook {
443 437
444 // ###################### Translations ###################### 438 // ###################### Translations ######################
445 439
446 private async addTranslations (plugin: PluginModel, npmName: string, translationPaths: PackagePluginTranslations) { 440 private async addTranslations (plugin: PluginModel, npmName: string, translationPaths: PluginTranslationPaths) {
447 for (const locale of Object.keys(translationPaths)) { 441 for (const locale of Object.keys(translationPaths)) {
448 const path = translationPaths[locale] 442 const path = translationPaths[locale]
449 const json = await readJSON(join(this.getPluginPath(plugin.name, plugin.type), path)) 443 const json = await readJSON(join(this.getPluginPath(plugin.name, plugin.type), path))
diff --git a/server/lib/plugins/register-helpers.ts b/server/lib/plugins/register-helpers.ts
index aa69ca2a2..f5b573370 100644
--- a/server/lib/plugins/register-helpers.ts
+++ b/server/lib/plugins/register-helpers.ts
@@ -26,10 +26,10 @@ import {
26 PluginVideoLicenceManager, 26 PluginVideoLicenceManager,
27 PluginVideoPrivacyManager, 27 PluginVideoPrivacyManager,
28 RegisterServerHookOptions, 28 RegisterServerHookOptions,
29 RegisterServerSettingOptions 29 RegisterServerSettingOptions,
30 serverHookObject
30} from '@shared/models' 31} from '@shared/models'
31import { serverHookObject } from '@shared/models/plugins/server-hook.model' 32import { VideoTranscodingProfilesManager } from '../transcoding/video-transcoding-profiles'
32import { VideoTranscodingProfilesManager } from '../video-transcoding-profiles'
33import { buildPluginHelpers } from './plugin-helpers-builder' 33import { buildPluginHelpers } from './plugin-helpers-builder'
34 34
35type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy' 35type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy'
diff --git a/server/lib/redundancy.ts b/server/lib/redundancy.ts
index da620b607..2a9241249 100644
--- a/server/lib/redundancy.ts
+++ b/server/lib/redundancy.ts
@@ -1,12 +1,12 @@
1import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
2import { sendUndoCacheFile } from './activitypub/send'
3import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
4import { MActorSignature, MVideoRedundancyVideo } from '@server/types/models'
5import { CONFIG } from '@server/initializers/config'
6import { logger } from '@server/helpers/logger' 2import { logger } from '@server/helpers/logger'
7import { ActorFollowModel } from '@server/models/activitypub/actor-follow' 3import { CONFIG } from '@server/initializers/config'
8import { Activity } from '@shared/models' 4import { ActorFollowModel } from '@server/models/actor/actor-follow'
9import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
6import { MActorSignature, MVideoRedundancyVideo } from '@server/types/models'
7import { Activity } from '@shared/models'
8import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
9import { sendUndoCacheFile } from './activitypub/send'
10 10
11async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) { 11async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) {
12 const serverActor = await getServerActor() 12 const serverActor = await getServerActor()
diff --git a/server/lib/schedulers/actor-follow-scheduler.ts b/server/lib/schedulers/actor-follow-scheduler.ts
index 598c0211f..1b80316e9 100644
--- a/server/lib/schedulers/actor-follow-scheduler.ts
+++ b/server/lib/schedulers/actor-follow-scheduler.ts
@@ -1,9 +1,9 @@
1import { isTestInstance } from '../../helpers/core-utils' 1import { isTestInstance } from '../../helpers/core-utils'
2import { logger } from '../../helpers/logger' 2import { logger } from '../../helpers/logger'
3import { ActorFollowModel } from '../../models/activitypub/actor-follow'
4import { AbstractScheduler } from './abstract-scheduler'
5import { ACTOR_FOLLOW_SCORE, SCHEDULER_INTERVALS_MS } from '../../initializers/constants' 3import { ACTOR_FOLLOW_SCORE, SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
4import { ActorFollowModel } from '../../models/actor/actor-follow'
6import { ActorFollowScoreCache } from '../files-cache' 5import { ActorFollowScoreCache } from '../files-cache'
6import { AbstractScheduler } from './abstract-scheduler'
7 7
8export class ActorFollowScheduler extends AbstractScheduler { 8export class ActorFollowScheduler extends AbstractScheduler {
9 9
diff --git a/server/lib/schedulers/auto-follow-index-instances.ts b/server/lib/schedulers/auto-follow-index-instances.ts
index 0b8cd1389..aaa5feed5 100644
--- a/server/lib/schedulers/auto-follow-index-instances.ts
+++ b/server/lib/schedulers/auto-follow-index-instances.ts
@@ -1,7 +1,7 @@
1import { chunk } from 'lodash' 1import { chunk } from 'lodash'
2import { doJSONRequest } from '@server/helpers/requests' 2import { doJSONRequest } from '@server/helpers/requests'
3import { JobQueue } from '@server/lib/job-queue' 3import { JobQueue } from '@server/lib/job-queue'
4import { ActorFollowModel } from '@server/models/activitypub/actor-follow' 4import { ActorFollowModel } from '@server/models/actor/actor-follow'
5import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
6import { logger } from '../../helpers/logger' 6import { logger } from '../../helpers/logger'
7import { CONFIG } from '../../initializers/config' 7import { CONFIG } from '../../initializers/config'
diff --git a/server/lib/schedulers/remove-old-history-scheduler.ts b/server/lib/schedulers/remove-old-history-scheduler.ts
index 17a42b2c4..225669ea2 100644
--- a/server/lib/schedulers/remove-old-history-scheduler.ts
+++ b/server/lib/schedulers/remove-old-history-scheduler.ts
@@ -1,7 +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' 4import { UserVideoHistoryModel } from '../../models/user/user-video-history'
5import { CONFIG } from '../../initializers/config' 5import { CONFIG } from '../../initializers/config'
6 6
7export class RemoveOldHistoryScheduler extends AbstractScheduler { 7export class RemoveOldHistoryScheduler extends AbstractScheduler {
diff --git a/server/lib/schedulers/youtube-dl-update-scheduler.ts b/server/lib/schedulers/youtube-dl-update-scheduler.ts
index aefe6aba4..898691c13 100644
--- a/server/lib/schedulers/youtube-dl-update-scheduler.ts
+++ b/server/lib/schedulers/youtube-dl-update-scheduler.ts
@@ -1,6 +1,6 @@
1import { AbstractScheduler } from './abstract-scheduler' 1import { YoutubeDL } from '@server/helpers/youtube-dl'
2import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' 2import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
3import { updateYoutubeDLBinary } from '../../helpers/youtube-dl' 3import { AbstractScheduler } from './abstract-scheduler'
4 4
5export class YoutubeDlUpdateScheduler extends AbstractScheduler { 5export class YoutubeDlUpdateScheduler extends AbstractScheduler {
6 6
@@ -13,7 +13,7 @@ export class YoutubeDlUpdateScheduler extends AbstractScheduler {
13 } 13 }
14 14
15 protected internalExecute () { 15 protected internalExecute () {
16 return updateYoutubeDLBinary() 16 return YoutubeDL.updateYoutubeDLBinary()
17 } 17 }
18 18
19 static get Instance () { 19 static get Instance () {
diff --git a/server/lib/stat-manager.ts b/server/lib/stat-manager.ts
index 09ba208bd..25ed21927 100644
--- a/server/lib/stat-manager.ts
+++ b/server/lib/stat-manager.ts
@@ -1,6 +1,6 @@
1import { CONFIG } from '@server/initializers/config' 1import { CONFIG } from '@server/initializers/config'
2import { UserModel } from '@server/models/account/user' 2import { UserModel } from '@server/models/user/user'
3import { ActorFollowModel } from '@server/models/activitypub/actor-follow' 3import { ActorFollowModel } from '@server/models/actor/actor-follow'
4import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy' 4import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy'
5import { VideoModel } from '@server/models/video/video' 5import { VideoModel } from '@server/models/video/video'
6import { VideoChannelModel } from '@server/models/video/video-channel' 6import { VideoChannelModel } from '@server/models/video/video-channel'
diff --git a/server/lib/video-transcoding-profiles.ts b/server/lib/transcoding/video-transcoding-profiles.ts
index 81f5e1962..c5ea72a5f 100644
--- a/server/lib/video-transcoding-profiles.ts
+++ b/server/lib/transcoding/video-transcoding-profiles.ts
@@ -1,6 +1,6 @@
1import { logger } from '@server/helpers/logger' 1import { logger } from '@server/helpers/logger'
2import { AvailableEncoders, EncoderOptionsBuilder, getTargetBitrate, VideoResolution } from '../../shared/models/videos' 2import { AvailableEncoders, EncoderOptionsBuilder, getTargetBitrate, VideoResolution } from '../../../shared/models/videos'
3import { buildStreamSuffix, resetSupportedEncoders } from '../helpers/ffmpeg-utils' 3import { buildStreamSuffix, resetSupportedEncoders } from '../../helpers/ffmpeg-utils'
4import { 4import {
5 canDoQuickAudioTranscode, 5 canDoQuickAudioTranscode,
6 ffprobePromise, 6 ffprobePromise,
@@ -8,8 +8,8 @@ import {
8 getMaxAudioBitrate, 8 getMaxAudioBitrate,
9 getVideoFileBitrate, 9 getVideoFileBitrate,
10 getVideoStreamFromFile 10 getVideoStreamFromFile
11} from '../helpers/ffprobe-utils' 11} from '../../helpers/ffprobe-utils'
12import { VIDEO_TRANSCODING_FPS } from '../initializers/constants' 12import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants'
13 13
14/** 14/**
15 * 15 *
diff --git a/server/lib/video-transcoding.ts b/server/lib/transcoding/video-transcoding.ts
index c949dca2e..5df192575 100644
--- a/server/lib/video-transcoding.ts
+++ b/server/lib/transcoding/video-transcoding.ts
@@ -3,17 +3,17 @@ import { copyFile, ensureDir, move, remove, stat } from 'fs-extra'
3import { basename, extname as extnameUtil, join } from 'path' 3import { basename, extname as extnameUtil, join } from 'path'
4import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 4import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
5import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoFullLight } from '@server/types/models' 5import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
6import { VideoResolution } from '../../shared/models/videos' 6import { VideoResolution } from '../../../shared/models/videos'
7import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' 7import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
8import { transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils' 8import { transcode, TranscodeOptions, TranscodeOptionsType } from '../../helpers/ffmpeg-utils'
9import { canDoQuickTranscode, getDurationFromVideoFile, getMetadataFromFile, getVideoFileFPS } from '../helpers/ffprobe-utils' 9import { canDoQuickTranscode, getDurationFromVideoFile, getMetadataFromFile, getVideoFileFPS } from '../../helpers/ffprobe-utils'
10import { logger } from '../helpers/logger' 10import { logger } from '../../helpers/logger'
11import { CONFIG } from '../initializers/config' 11import { CONFIG } from '../../initializers/config'
12import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' 12import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../../initializers/constants'
13import { VideoFileModel } from '../models/video/video-file' 13import { VideoFileModel } from '../../models/video/video-file'
14import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' 14import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
15import { updateMasterHLSPlaylist, updateSha256VODSegments } from './hls' 15import { updateMasterHLSPlaylist, updateSha256VODSegments } from '../hls'
16import { generateVideoFilename, generateVideoStreamingPlaylistName, getVideoFilePath } from './video-paths' 16import { generateVideoFilename, generateVideoStreamingPlaylistName, getVideoFilePath } from '../video-paths'
17import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' 17import { VideoTranscodingProfilesManager } from './video-transcoding-profiles'
18 18
19/** 19/**
@@ -215,16 +215,6 @@ function generateHlsPlaylistResolution (options: {
215 }) 215 })
216} 216}
217 217
218function getEnabledResolutions (type: 'vod' | 'live') {
219 const transcoding = type === 'vod'
220 ? CONFIG.TRANSCODING
221 : CONFIG.LIVE.TRANSCODING
222
223 return Object.keys(transcoding.RESOLUTIONS)
224 .filter(key => transcoding.ENABLED && transcoding.RESOLUTIONS[key] === true)
225 .map(r => parseInt(r, 10))
226}
227
228// --------------------------------------------------------------------------- 218// ---------------------------------------------------------------------------
229 219
230export { 220export {
@@ -232,8 +222,7 @@ export {
232 generateHlsPlaylistResolutionFromTS, 222 generateHlsPlaylistResolutionFromTS,
233 optimizeOriginalVideofile, 223 optimizeOriginalVideofile,
234 transcodeNewWebTorrentResolution, 224 transcodeNewWebTorrentResolution,
235 mergeAudioVideofile, 225 mergeAudioVideofile
236 getEnabledResolutions
237} 226}
238 227
239// --------------------------------------------------------------------------- 228// ---------------------------------------------------------------------------
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 9b0a0a2f1..8a6fcebc7 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -1,14 +1,15 @@
1import { Transaction } from 'sequelize/types' 1import { Transaction } from 'sequelize/types'
2import { v4 as uuidv4 } from 'uuid' 2import { v4 as uuidv4 } from 'uuid'
3import { UserModel } from '@server/models/account/user' 3import { UserModel } from '@server/models/user/user'
4import { MActorDefault } from '@server/types/models/actor'
4import { ActivityPubActorType } from '../../shared/models/activitypub' 5import { ActivityPubActorType } from '../../shared/models/activitypub'
5import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' 6import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users'
6import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' 7import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants'
7import { sequelizeTypescript } from '../initializers/database' 8import { sequelizeTypescript } from '../initializers/database'
8import { AccountModel } from '../models/account/account' 9import { AccountModel } from '../models/account/account'
9import { UserNotificationSettingModel } from '../models/account/user-notification-setting' 10import { ActorModel } from '../models/actor/actor'
10import { ActorModel } from '../models/activitypub/actor' 11import { UserNotificationSettingModel } from '../models/user/user-notification-setting'
11import { MAccountDefault, MActorDefault, MChannelActor } from '../types/models' 12import { MAccountDefault, MChannelActor } from '../types/models'
12import { MUser, MUserDefault, MUserId } from '../types/models/user' 13import { MUser, MUserDefault, MUserId } from '../types/models/user'
13import { buildActorInstance, generateAndSaveActorKeys } from './activitypub/actor' 14import { buildActorInstance, generateAndSaveActorKeys } from './activitypub/actor'
14import { getLocalAccountActivityPubUrl } from './activitypub/url' 15import { getLocalAccountActivityPubUrl } from './activitypub/url'
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts
index 0476cb2d5..d57e832fe 100644
--- a/server/lib/video-channel.ts
+++ b/server/lib/video-channel.ts
@@ -1,5 +1,4 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { v4 as uuidv4 } from 'uuid'
3import { VideoChannelCreate } from '../../shared/models' 2import { VideoChannelCreate } from '../../shared/models'
4import { VideoModel } from '../models/video/video' 3import { VideoModel } from '../models/video/video'
5import { VideoChannelModel } from '../models/video/video-channel' 4import { VideoChannelModel } from '../models/video/video-channel'
@@ -9,9 +8,8 @@ import { getLocalVideoChannelActivityPubUrl } from './activitypub/url'
9import { federateVideoIfNeeded } from './activitypub/videos' 8import { federateVideoIfNeeded } from './activitypub/videos'
10 9
11async function createLocalVideoChannel (videoChannelInfo: VideoChannelCreate, account: MAccountId, t: Sequelize.Transaction) { 10async function createLocalVideoChannel (videoChannelInfo: VideoChannelCreate, account: MAccountId, t: Sequelize.Transaction) {
12 const uuid = uuidv4()
13 const url = getLocalVideoChannelActivityPubUrl(videoChannelInfo.name) 11 const url = getLocalVideoChannelActivityPubUrl(videoChannelInfo.name)
14 const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid) 12 const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name)
15 13
16 const actorInstanceCreated = await actorInstance.save({ transaction: t }) 14 const actorInstanceCreated = await actorInstance.save({ transaction: t })
17 15
diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts
index 736ebb2f8..51a9c747e 100644
--- a/server/lib/video-comment.ts
+++ b/server/lib/video-comment.ts
@@ -3,7 +3,7 @@ import * as Sequelize from 'sequelize'
3import { logger } from '@server/helpers/logger' 3import { logger } from '@server/helpers/logger'
4import { sequelizeTypescript } from '@server/initializers/database' 4import { sequelizeTypescript } from '@server/initializers/database'
5import { ResultList } from '../../shared/models' 5import { ResultList } from '../../shared/models'
6import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' 6import { VideoCommentThreadTree } from '../../shared/models/videos/comment/video-comment.model'
7import { VideoCommentModel } from '../models/video/video-comment' 7import { VideoCommentModel } from '../models/video/video-comment'
8import { MAccountDefault, MComment, MCommentOwnerVideo, MCommentOwnerVideoReply, MVideoFullLight } from '../types/models' 8import { MAccountDefault, MComment, MCommentOwnerVideo, MCommentOwnerVideoReply, MVideoFullLight } from '../types/models'
9import { sendCreateVideoComment, sendDeleteVideoComment } from './activitypub/send' 9import { sendCreateVideoComment, sendDeleteVideoComment } from './activitypub/send'
diff --git a/server/lib/video.ts b/server/lib/video.ts
index 21e4b7ff2..d26cf85cd 100644
--- a/server/lib/video.ts
+++ b/server/lib/video.ts
@@ -28,6 +28,8 @@ function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): Fil
28 privacy: videoInfo.privacy || VideoPrivacy.PRIVATE, 28 privacy: videoInfo.privacy || VideoPrivacy.PRIVATE,
29 channelId: channelId, 29 channelId: channelId,
30 originallyPublishedAt: videoInfo.originallyPublishedAt 30 originallyPublishedAt: videoInfo.originallyPublishedAt
31 ? new Date(videoInfo.originallyPublishedAt)
32 : null
31 } 33 }
32} 34}
33 35
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts
index bb849dc72..1d18de8cd 100644
--- a/server/middlewares/validators/follows.ts
+++ b/server/middlewares/validators/follows.ts
@@ -1,18 +1,18 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param, query } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { isFollowStateValid } from '@server/helpers/custom-validators/follows'
4import { getServerActor } from '@server/models/application/application'
5import { MActorFollowActorsDefault } from '@server/types/models'
6import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
3import { isTestInstance } from '../../helpers/core-utils' 7import { isTestInstance } from '../../helpers/core-utils'
8import { isActorTypeValid, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor'
4import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' 9import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers'
5import { logger } from '../../helpers/logger' 10import { logger } from '../../helpers/logger'
11import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
6import { SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' 12import { SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants'
7import { ActorFollowModel } from '../../models/activitypub/actor-follow' 13import { ActorModel } from '../../models/actor/actor'
14import { ActorFollowModel } from '../../models/actor/actor-follow'
8import { areValidationErrors } from './utils' 15import { areValidationErrors } from './utils'
9import { ActorModel } from '../../models/activitypub/actor'
10import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
11import { isActorTypeValid, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor'
12import { MActorFollowActorsDefault } from '@server/types/models'
13import { isFollowStateValid } from '@server/helpers/custom-validators/follows'
14import { getServerActor } from '@server/models/application/application'
15import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
16 16
17const listFollowsValidator = [ 17const listFollowsValidator = [
18 query('state') 18 query('state')
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts
index ab87fe720..2c47ec5bb 100644
--- a/server/middlewares/validators/plugins.ts
+++ b/server/middlewares/validators/plugins.ts
@@ -1,15 +1,15 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param, query, ValidationChain } from 'express-validator' 2import { body, param, query, ValidationChain } from 'express-validator'
3import { logger } from '../../helpers/logger' 3import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
4import { areValidationErrors } from './utils' 4import { PluginType } from '../../../shared/models/plugins/plugin.type'
5import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/server/api/install-plugin.model'
6import { exists, isBooleanValid, isSafePath, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
5import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' 7import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins'
8import { logger } from '../../helpers/logger'
9import { CONFIG } from '../../initializers/config'
6import { PluginManager } from '../../lib/plugins/plugin-manager' 10import { PluginManager } from '../../lib/plugins/plugin-manager'
7import { isBooleanValid, isSafePath, toBooleanOrNull, exists, toIntOrNull } from '../../helpers/custom-validators/misc'
8import { PluginModel } from '../../models/server/plugin' 11import { PluginModel } from '../../models/server/plugin'
9import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model' 12import { areValidationErrors } from './utils'
10import { PluginType } from '../../../shared/models/plugins/plugin.type'
11import { CONFIG } from '../../initializers/config'
12import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
13 13
14const getPluginValidator = (pluginType: PluginType, withVersion = true) => { 14const getPluginValidator = (pluginType: PluginType, withVersion = true) => {
15 const validators: (ValidationChain | express.Handler)[] = [ 15 const validators: (ValidationChain | express.Handler)[] = [
diff --git a/server/middlewares/validators/user-subscriptions.ts b/server/middlewares/validators/user-subscriptions.ts
index 0d0c8ccbf..1823892b6 100644
--- a/server/middlewares/validators/user-subscriptions.ts
+++ b/server/middlewares/validators/user-subscriptions.ts
@@ -1,12 +1,12 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param, query } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { logger } from '../../helpers/logger' 3import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
4import { areValidationErrors } from './utils'
5import { ActorFollowModel } from '../../models/activitypub/actor-follow'
6import { areValidActorHandles, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' 4import { areValidActorHandles, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor'
7import { toArray } from '../../helpers/custom-validators/misc' 5import { toArray } from '../../helpers/custom-validators/misc'
6import { logger } from '../../helpers/logger'
8import { WEBSERVER } from '../../initializers/constants' 7import { WEBSERVER } from '../../initializers/constants'
9import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' 8import { ActorFollowModel } from '../../models/actor/actor-follow'
9import { areValidationErrors } from './utils'
10 10
11const userSubscriptionListValidator = [ 11const userSubscriptionListValidator = [
12 query('search').optional().not().isEmpty().withMessage('Should have a valid search'), 12 query('search').optional().not().isEmpty().withMessage('Should have a valid search'),
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index 37119e279..548d5df4d 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -34,8 +34,8 @@ import { doesVideoExist } from '../../helpers/middlewares'
34import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' 34import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
35import { isThemeRegistered } from '../../lib/plugins/theme-utils' 35import { isThemeRegistered } from '../../lib/plugins/theme-utils'
36import { Redis } from '../../lib/redis' 36import { Redis } from '../../lib/redis'
37import { UserModel } from '../../models/account/user' 37import { UserModel } from '../../models/user/user'
38import { ActorModel } from '../../models/activitypub/actor' 38import { ActorModel } from '../../models/actor/actor'
39import { areValidationErrors } from './utils' 39import { areValidationErrors } from './utils'
40 40
41const usersListValidator = [ 41const usersListValidator = [
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts
index 2463d281c..e881f0d3e 100644
--- a/server/middlewares/validators/videos/video-channels.ts
+++ b/server/middlewares/validators/videos/video-channels.ts
@@ -3,6 +3,7 @@ import { body, param, query } from 'express-validator'
3import { VIDEO_CHANNELS } from '@server/initializers/constants' 3import { VIDEO_CHANNELS } from '@server/initializers/constants'
4import { MChannelAccountDefault, MUser } from '@server/types/models' 4import { MChannelAccountDefault, MUser } from '@server/types/models'
5import { UserRight } from '../../../../shared' 5import { UserRight } from '../../../../shared'
6import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
6import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' 7import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor'
7import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' 8import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc'
8import { 9import {
@@ -12,10 +13,9 @@ import {
12} from '../../../helpers/custom-validators/video-channels' 13} from '../../../helpers/custom-validators/video-channels'
13import { logger } from '../../../helpers/logger' 14import { logger } from '../../../helpers/logger'
14import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares' 15import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares'
15import { ActorModel } from '../../../models/activitypub/actor' 16import { ActorModel } from '../../../models/actor/actor'
16import { VideoChannelModel } from '../../../models/video/video-channel' 17import { VideoChannelModel } from '../../../models/video/video-channel'
17import { areValidationErrors } from '../utils' 18import { areValidationErrors } from '../utils'
18import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
19 19
20const videoChannelsAddValidator = [ 20const videoChannelsAddValidator = [
21 body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), 21 body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts
index c53af3861..d0643ff26 100644
--- a/server/middlewares/validators/videos/video-imports.ts
+++ b/server/middlewares/validators/videos/video-imports.ts
@@ -47,14 +47,12 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
47 cleanUpReqFiles(req) 47 cleanUpReqFiles(req)
48 return res.status(HttpStatusCode.CONFLICT_409) 48 return res.status(HttpStatusCode.CONFLICT_409)
49 .json({ error: 'HTTP import is not enabled on this instance.' }) 49 .json({ error: 'HTTP import is not enabled on this instance.' })
50 .end()
51 } 50 }
52 51
53 if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) { 52 if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) {
54 cleanUpReqFiles(req) 53 cleanUpReqFiles(req)
55 return res.status(HttpStatusCode.CONFLICT_409) 54 return res.status(HttpStatusCode.CONFLICT_409)
56 .json({ error: 'Torrent/magnet URI import is not enabled on this instance.' }) 55 .json({ error: 'Torrent/magnet URI import is not enabled on this instance.' })
57 .end()
58 } 56 }
59 57
60 if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) 58 if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
@@ -65,7 +63,6 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
65 63
66 return res.status(HttpStatusCode.BAD_REQUEST_400) 64 return res.status(HttpStatusCode.BAD_REQUEST_400)
67 .json({ error: 'Should have a magnetUri or a targetUrl or a torrent file.' }) 65 .json({ error: 'Should have a magnetUri or a targetUrl or a torrent file.' })
68 .end()
69 } 66 }
70 67
71 if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req) 68 if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req)
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index d26bcd4a6..3219e10d4 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -7,7 +7,7 @@ import { ExpressPromiseHandler } from '@server/types/express'
7import { MUserAccountId, MVideoWithRights } from '@server/types/models' 7import { MUserAccountId, MVideoWithRights } from '@server/types/models'
8import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared' 8import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared'
9import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 9import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
10import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' 10import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/change-ownership/video-change-ownership-accept.model'
11import { 11import {
12 exists, 12 exists,
13 isBooleanValid, 13 isBooleanValid,
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts
index a71422ed8..c2dfccc96 100644
--- a/server/middlewares/validators/webfinger.ts
+++ b/server/middlewares/validators/webfinger.ts
@@ -1,11 +1,11 @@
1import * as express from 'express' 1import * as express from 'express'
2import { query } from 'express-validator' 2import { query } from 'express-validator'
3import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
3import { isWebfingerLocalResourceValid } from '../../helpers/custom-validators/webfinger' 4import { isWebfingerLocalResourceValid } from '../../helpers/custom-validators/webfinger'
5import { getHostWithPort } from '../../helpers/express-utils'
4import { logger } from '../../helpers/logger' 6import { logger } from '../../helpers/logger'
5import { ActorModel } from '../../models/activitypub/actor' 7import { ActorModel } from '../../models/actor/actor'
6import { areValidationErrors } from './utils' 8import { areValidationErrors } from './utils'
7import { getHostWithPort } from '../../helpers/express-utils'
8import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
9 9
10const webfingerValidator = [ 10const webfingerValidator = [
11 query('resource').custom(isWebfingerLocalResourceValid).withMessage('Should have a valid webfinger resource'), 11 query('resource').custom(isWebfingerLocalResourceValid).withMessage('Should have a valid webfinger resource'),
diff --git a/server/models/abuse/abuse-message.ts b/server/models/abuse/abuse-message.ts
index 7e51b3e07..2c5987e96 100644
--- a/server/models/abuse/abuse-message.ts
+++ b/server/models/abuse/abuse-message.ts
@@ -1,6 +1,7 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { isAbuseMessageValid } from '@server/helpers/custom-validators/abuses' 2import { isAbuseMessageValid } from '@server/helpers/custom-validators/abuses'
3import { MAbuseMessage, MAbuseMessageFormattable } from '@server/types/models' 3import { MAbuseMessage, MAbuseMessageFormattable } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils'
4import { AbuseMessage } from '@shared/models' 5import { AbuseMessage } from '@shared/models'
5import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' 6import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
6import { getSort, throwIfNotValid } from '../utils' 7import { getSort, throwIfNotValid } from '../utils'
@@ -17,7 +18,7 @@ import { AbuseModel } from './abuse'
17 } 18 }
18 ] 19 ]
19}) 20})
20export class AbuseMessageModel extends Model { 21export class AbuseMessageModel extends Model<Partial<AttributesOnly<AbuseMessageModel>>> {
21 22
22 @AllowNull(false) 23 @AllowNull(false)
23 @Is('AbuseMessage', value => throwIfNotValid(value, isAbuseMessageValid, 'message')) 24 @Is('AbuseMessage', value => throwIfNotValid(value, isAbuseMessageValid, 'message'))
diff --git a/server/models/abuse/abuse.ts b/server/models/abuse/abuse.ts
index 262f364f1..3518f5c02 100644
--- a/server/models/abuse/abuse.ts
+++ b/server/models/abuse/abuse.ts
@@ -16,7 +16,7 @@ import {
16 UpdatedAt 16 UpdatedAt
17} from 'sequelize-typescript' 17} from 'sequelize-typescript'
18import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses' 18import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses'
19import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' 19import { abusePredefinedReasonsMap, AttributesOnly } from '@shared/core-utils'
20import { 20import {
21 AbuseFilter, 21 AbuseFilter,
22 AbuseObject, 22 AbuseObject,
@@ -187,7 +187,7 @@ export enum ScopeNames {
187 } 187 }
188 ] 188 ]
189}) 189})
190export class AbuseModel extends Model { 190export class AbuseModel extends Model<Partial<AttributesOnly<AbuseModel>>> {
191 191
192 @AllowNull(false) 192 @AllowNull(false)
193 @Default(null) 193 @Default(null)
diff --git a/server/models/abuse/video-abuse.ts b/server/models/abuse/video-abuse.ts
index 90aa0695e..95bff50d0 100644
--- a/server/models/abuse/video-abuse.ts
+++ b/server/models/abuse/video-abuse.ts
@@ -1,4 +1,5 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { AttributesOnly } from '@shared/core-utils'
2import { VideoDetails } from '@shared/models' 3import { VideoDetails } from '@shared/models'
3import { VideoModel } from '../video/video' 4import { VideoModel } from '../video/video'
4import { AbuseModel } from './abuse' 5import { AbuseModel } from './abuse'
@@ -14,7 +15,7 @@ import { AbuseModel } from './abuse'
14 } 15 }
15 ] 16 ]
16}) 17})
17export class VideoAbuseModel extends Model { 18export class VideoAbuseModel extends Model<Partial<AttributesOnly<VideoAbuseModel>>> {
18 19
19 @CreatedAt 20 @CreatedAt
20 createdAt: Date 21 createdAt: Date
diff --git a/server/models/abuse/video-comment-abuse.ts b/server/models/abuse/video-comment-abuse.ts
index d3fce76a5..32cb2ca64 100644
--- a/server/models/abuse/video-comment-abuse.ts
+++ b/server/models/abuse/video-comment-abuse.ts
@@ -1,4 +1,5 @@
1import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { AttributesOnly } from '@shared/core-utils'
2import { VideoCommentModel } from '../video/video-comment' 3import { VideoCommentModel } from '../video/video-comment'
3import { AbuseModel } from './abuse' 4import { AbuseModel } from './abuse'
4 5
@@ -13,7 +14,7 @@ import { AbuseModel } from './abuse'
13 } 14 }
14 ] 15 ]
15}) 16})
16export class VideoCommentAbuseModel extends Model { 17export class VideoCommentAbuseModel extends Model<Partial<AttributesOnly<VideoCommentAbuseModel>>> {
17 18
18 @CreatedAt 19 @CreatedAt
19 createdAt: Date 20 createdAt: Date
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts
index fe9168ab8..b2375b006 100644
--- a/server/models/account/account-blocklist.ts
+++ b/server/models/account/account-blocklist.ts
@@ -1,8 +1,9 @@
1import { Op } from 'sequelize' 1import { Op } from 'sequelize'
2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models' 3import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils'
4import { AccountBlock } from '../../../shared/models' 5import { AccountBlock } from '../../../shared/models'
5import { ActorModel } from '../activitypub/actor' 6import { ActorModel } from '../actor/actor'
6import { ServerModel } from '../server/server' 7import { ServerModel } from '../server/server'
7import { getSort, searchAttribute } from '../utils' 8import { getSort, searchAttribute } from '../utils'
8import { AccountModel } from './account' 9import { AccountModel } from './account'
@@ -40,7 +41,7 @@ enum ScopeNames {
40 } 41 }
41 ] 42 ]
42}) 43})
43export class AccountBlocklistModel extends Model { 44export class AccountBlocklistModel extends Model<Partial<AttributesOnly<AccountBlocklistModel>>> {
44 45
45 @CreatedAt 46 @CreatedAt
46 createdAt: Date 47 createdAt: Date
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts
index 801f76bba..ee6dbc6da 100644
--- a/server/models/account/account-video-rate.ts
+++ b/server/models/account/account-video-rate.ts
@@ -7,11 +7,12 @@ import {
7 MAccountVideoRateAccountVideo, 7 MAccountVideoRateAccountVideo,
8 MAccountVideoRateFormattable 8 MAccountVideoRateFormattable
9} from '@server/types/models/video/video-rate' 9} from '@server/types/models/video/video-rate'
10import { AttributesOnly } from '@shared/core-utils'
10import { AccountVideoRate } from '../../../shared' 11import { AccountVideoRate } from '../../../shared'
11import { VideoRateType } from '../../../shared/models/videos' 12import { VideoRateType } from '../../../shared/models/videos'
12import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 13import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
13import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants' 14import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants'
14import { ActorModel } from '../activitypub/actor' 15import { ActorModel } from '../actor/actor'
15import { buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' 16import { buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils'
16import { VideoModel } from '../video/video' 17import { VideoModel } from '../video/video'
17import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' 18import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel'
@@ -42,7 +43,7 @@ import { AccountModel } from './account'
42 } 43 }
43 ] 44 ]
44}) 45})
45export class AccountVideoRateModel extends Model { 46export class AccountVideoRateModel extends Model<Partial<AttributesOnly<AccountVideoRateModel>>> {
46 47
47 @AllowNull(false) 48 @AllowNull(false)
48 @Column(DataType.ENUM(...values(VIDEO_RATE_TYPES))) 49 @Column(DataType.ENUM(...values(VIDEO_RATE_TYPES)))
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index d33353af7..665ecd595 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -17,10 +17,11 @@ import {
17 UpdatedAt 17 UpdatedAt
18} from 'sequelize-typescript' 18} from 'sequelize-typescript'
19import { ModelCache } from '@server/models/model-cache' 19import { ModelCache } from '@server/models/model-cache'
20import { AttributesOnly } from '@shared/core-utils'
20import { Account, AccountSummary } from '../../../shared/models/actors' 21import { Account, AccountSummary } from '../../../shared/models/actors'
21import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts' 22import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts'
22import { CONSTRAINTS_FIELDS, SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' 23import { CONSTRAINTS_FIELDS, SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants'
23import { sendDeleteActor } from '../../lib/activitypub/send' 24import { sendDeleteActor } from '../../lib/activitypub/send/send-delete'
24import { 25import {
25 MAccount, 26 MAccount,
26 MAccountActor, 27 MAccountActor,
@@ -30,19 +31,19 @@ import {
30 MAccountSummaryFormattable, 31 MAccountSummaryFormattable,
31 MChannelActor 32 MChannelActor
32} from '../../types/models' 33} from '../../types/models'
33import { ActorModel } from '../activitypub/actor' 34import { ActorModel } from '../actor/actor'
34import { ActorFollowModel } from '../activitypub/actor-follow' 35import { ActorFollowModel } from '../actor/actor-follow'
36import { ActorImageModel } from '../actor/actor-image'
35import { ApplicationModel } from '../application/application' 37import { ApplicationModel } from '../application/application'
36import { ActorImageModel } from './actor-image'
37import { ServerModel } from '../server/server' 38import { ServerModel } from '../server/server'
38import { ServerBlocklistModel } from '../server/server-blocklist' 39import { ServerBlocklistModel } from '../server/server-blocklist'
40import { UserModel } from '../user/user'
39import { getSort, throwIfNotValid } from '../utils' 41import { getSort, throwIfNotValid } from '../utils'
40import { VideoModel } from '../video/video' 42import { VideoModel } from '../video/video'
41import { VideoChannelModel } from '../video/video-channel' 43import { VideoChannelModel } from '../video/video-channel'
42import { VideoCommentModel } from '../video/video-comment' 44import { VideoCommentModel } from '../video/video-comment'
43import { VideoPlaylistModel } from '../video/video-playlist' 45import { VideoPlaylistModel } from '../video/video-playlist'
44import { AccountBlocklistModel } from './account-blocklist' 46import { AccountBlocklistModel } from './account-blocklist'
45import { UserModel } from './user'
46 47
47export enum ScopeNames { 48export enum ScopeNames {
48 SUMMARY = 'SUMMARY' 49 SUMMARY = 'SUMMARY'
@@ -141,7 +142,7 @@ export type SummaryOptions = {
141 } 142 }
142 ] 143 ]
143}) 144})
144export class AccountModel extends Model { 145export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> {
145 146
146 @AllowNull(false) 147 @AllowNull(false)
147 @Column 148 @Column
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/actor/actor-follow.ts
index 4c5f37620..3a09e51d6 100644
--- a/server/models/activitypub/actor-follow.ts
+++ b/server/models/actor/actor-follow.ts
@@ -28,6 +28,7 @@ import {
28 MActorFollowFormattable, 28 MActorFollowFormattable,
29 MActorFollowSubscriptions 29 MActorFollowSubscriptions
30} from '@server/types/models' 30} from '@server/types/models'
31import { AttributesOnly } from '@shared/core-utils'
31import { ActivityPubActorType } from '@shared/models' 32import { ActivityPubActorType } from '@shared/models'
32import { FollowState } from '../../../shared/models/actors' 33import { FollowState } from '../../../shared/models/actors'
33import { ActorFollow } from '../../../shared/models/actors/follow.model' 34import { ActorFollow } from '../../../shared/models/actors/follow.model'
@@ -61,7 +62,7 @@ import { ActorModel, unusedActorAttributesForAPI } from './actor'
61 } 62 }
62 ] 63 ]
63}) 64})
64export class ActorFollowModel extends Model { 65export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowModel>>> {
65 66
66 @AllowNull(false) 67 @AllowNull(false)
67 @Column(DataType.ENUM(...values(FOLLOW_STATES))) 68 @Column(DataType.ENUM(...values(FOLLOW_STATES)))
@@ -619,7 +620,7 @@ export class ActorFollowModel extends Model {
619 if (serverIds.length === 0) return 620 if (serverIds.length === 0) return
620 621
621 const me = await getServerActor() 622 const me = await getServerActor()
622 const serverIdsString = createSafeIn(ActorFollowModel, serverIds) 623 const serverIdsString = createSafeIn(ActorFollowModel.sequelize, serverIds)
623 624
624 const query = `UPDATE "actorFollow" SET "score" = LEAST("score" + ${value}, ${ACTOR_FOLLOW_SCORE.MAX}) ` + 625 const query = `UPDATE "actorFollow" SET "score" = LEAST("score" + ${value}, ${ACTOR_FOLLOW_SCORE.MAX}) ` +
625 'WHERE id IN (' + 626 'WHERE id IN (' +
diff --git a/server/models/account/actor-image.ts b/server/models/actor/actor-image.ts
index ae05b4969..a35f9edb0 100644
--- a/server/models/account/actor-image.ts
+++ b/server/models/actor/actor-image.ts
@@ -2,6 +2,7 @@ import { remove } from 'fs-extra'
2import { join } from 'path' 2import { join } from 'path'
3import { AfterDestroy, AllowNull, Column, CreatedAt, Default, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 3import { AfterDestroy, AllowNull, Column, CreatedAt, Default, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
4import { MActorImageFormattable } from '@server/types/models' 4import { MActorImageFormattable } from '@server/types/models'
5import { AttributesOnly } from '@shared/core-utils'
5import { ActorImageType } from '@shared/models' 6import { ActorImageType } from '@shared/models'
6import { ActorImage } from '../../../shared/models/actors/actor-image.model' 7import { ActorImage } from '../../../shared/models/actors/actor-image.model'
7import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 8import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
@@ -19,7 +20,7 @@ import { throwIfNotValid } from '../utils'
19 } 20 }
20 ] 21 ]
21}) 22})
22export class ActorImageModel extends Model { 23export class ActorImageModel extends Model<Partial<AttributesOnly<ActorImageModel>>> {
23 24
24 @AllowNull(false) 25 @AllowNull(false)
25 @Column 26 @Column
diff --git a/server/models/activitypub/actor.ts b/server/models/actor/actor.ts
index 1af9efac2..65c53f8f8 100644
--- a/server/models/activitypub/actor.ts
+++ b/server/models/actor/actor.ts
@@ -18,6 +18,7 @@ import {
18 UpdatedAt 18 UpdatedAt
19} from 'sequelize-typescript' 19} from 'sequelize-typescript'
20import { ModelCache } from '@server/models/model-cache' 20import { ModelCache } from '@server/models/model-cache'
21import { AttributesOnly } from '@shared/core-utils'
21import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub' 22import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub'
22import { ActorImage } from '../../../shared/models/actors/actor-image.model' 23import { ActorImage } from '../../../shared/models/actors/actor-image.model'
23import { activityPubContextify } from '../../helpers/activitypub' 24import { activityPubContextify } from '../../helpers/activitypub'
@@ -51,12 +52,12 @@ import {
51 MActorWithInboxes 52 MActorWithInboxes
52} from '../../types/models' 53} from '../../types/models'
53import { AccountModel } from '../account/account' 54import { AccountModel } from '../account/account'
54import { ActorImageModel } from '../account/actor-image'
55import { ServerModel } from '../server/server' 55import { ServerModel } from '../server/server'
56import { isOutdated, throwIfNotValid } from '../utils' 56import { isOutdated, throwIfNotValid } from '../utils'
57import { VideoModel } from '../video/video' 57import { VideoModel } from '../video/video'
58import { VideoChannelModel } from '../video/video-channel' 58import { VideoChannelModel } from '../video/video-channel'
59import { ActorFollowModel } from './actor-follow' 59import { ActorFollowModel } from './actor-follow'
60import { ActorImageModel } from './actor-image'
60 61
61enum ScopeNames { 62enum ScopeNames {
62 FULL = 'FULL' 63 FULL = 'FULL'
@@ -159,7 +160,7 @@ export const unusedActorAttributesForAPI = [
159 } 160 }
160 ] 161 ]
161}) 162})
162export class ActorModel extends Model { 163export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
163 164
164 @AllowNull(false) 165 @AllowNull(false)
165 @Column(DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES))) 166 @Column(DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES)))
diff --git a/server/models/application/application.ts b/server/models/application/application.ts
index 21f8b1cbc..5531d134a 100644
--- a/server/models/application/application.ts
+++ b/server/models/application/application.ts
@@ -1,6 +1,7 @@
1import * as memoizee from 'memoizee'
1import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' 2import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript'
3import { AttributesOnly } from '@shared/core-utils'
2import { AccountModel } from '../account/account' 4import { AccountModel } from '../account/account'
3import * as memoizee from 'memoizee'
4 5
5export const getServerActor = memoizee(async function () { 6export const getServerActor = memoizee(async function () {
6 const application = await ApplicationModel.load() 7 const application = await ApplicationModel.load()
@@ -24,7 +25,7 @@ export const getServerActor = memoizee(async function () {
24 tableName: 'application', 25 tableName: 'application',
25 timestamps: false 26 timestamps: false
26}) 27})
27export class ApplicationModel extends Model { 28export class ApplicationModel extends Model<Partial<AttributesOnly<ApplicationModel>>> {
28 29
29 @AllowNull(false) 30 @AllowNull(false)
30 @Default(0) 31 @Default(0)
diff --git a/server/models/oauth/oauth-client.ts b/server/models/oauth/oauth-client.ts
index 8dbc1c2f5..890954bdb 100644
--- a/server/models/oauth/oauth-client.ts
+++ b/server/models/oauth/oauth-client.ts
@@ -1,4 +1,5 @@
1import { AllowNull, Column, CreatedAt, DataType, HasMany, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, Column, CreatedAt, DataType, HasMany, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { AttributesOnly } from '@shared/core-utils'
2import { OAuthTokenModel } from './oauth-token' 3import { OAuthTokenModel } from './oauth-token'
3 4
4@Table({ 5@Table({
@@ -14,7 +15,7 @@ import { OAuthTokenModel } from './oauth-token'
14 } 15 }
15 ] 16 ]
16}) 17})
17export class OAuthClientModel extends Model { 18export class OAuthClientModel extends Model<Partial<AttributesOnly<OAuthClientModel>>> {
18 19
19 @AllowNull(false) 20 @AllowNull(false)
20 @Column 21 @Column
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts
index 27e643aa7..af4b0ec42 100644
--- a/server/models/oauth/oauth-token.ts
+++ b/server/models/oauth/oauth-token.ts
@@ -15,10 +15,11 @@ import {
15import { TokensCache } from '@server/lib/auth/tokens-cache' 15import { TokensCache } from '@server/lib/auth/tokens-cache'
16import { MUserAccountId } from '@server/types/models' 16import { MUserAccountId } from '@server/types/models'
17import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token' 17import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token'
18import { AttributesOnly } from '@shared/core-utils'
18import { logger } from '../../helpers/logger' 19import { logger } from '../../helpers/logger'
19import { AccountModel } from '../account/account' 20import { AccountModel } from '../account/account'
20import { UserModel } from '../account/user' 21import { ActorModel } from '../actor/actor'
21import { ActorModel } from '../activitypub/actor' 22import { UserModel } from '../user/user'
22import { OAuthClientModel } from './oauth-client' 23import { OAuthClientModel } from './oauth-client'
23 24
24export type OAuthTokenInfo = { 25export type OAuthTokenInfo = {
@@ -78,7 +79,7 @@ enum ScopeNames {
78 } 79 }
79 ] 80 ]
80}) 81})
81export class OAuthTokenModel extends Model { 82export class OAuthTokenModel extends Model<Partial<AttributesOnly<OAuthTokenModel>>> {
82 83
83 @AllowNull(false) 84 @AllowNull(false)
84 @Column 85 @Column
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts
index 53ebadeaf..ef780c2a4 100644
--- a/server/models/redundancy/video-redundancy.ts
+++ b/server/models/redundancy/video-redundancy.ts
@@ -16,6 +16,7 @@ import {
16} from 'sequelize-typescript' 16} from 'sequelize-typescript'
17import { getServerActor } from '@server/models/application/application' 17import { getServerActor } from '@server/models/application/application'
18import { MActor, MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/types/models' 18import { MActor, MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/types/models'
19import { AttributesOnly } from '@shared/core-utils'
19import { VideoRedundanciesTarget } from '@shared/models/redundancy/video-redundancies-filters.model' 20import { VideoRedundanciesTarget } from '@shared/models/redundancy/video-redundancies-filters.model'
20import { 21import {
21 FileRedundancyInformation, 22 FileRedundancyInformation,
@@ -29,7 +30,7 @@ import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validato
29import { logger } from '../../helpers/logger' 30import { logger } from '../../helpers/logger'
30import { CONFIG } from '../../initializers/config' 31import { CONFIG } from '../../initializers/config'
31import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' 32import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants'
32import { ActorModel } from '../activitypub/actor' 33import { ActorModel } from '../actor/actor'
33import { ServerModel } from '../server/server' 34import { ServerModel } from '../server/server'
34import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils' 35import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils'
35import { ScheduleVideoUpdateModel } from '../video/schedule-video-update' 36import { ScheduleVideoUpdateModel } from '../video/schedule-video-update'
@@ -84,7 +85,7 @@ export enum ScopeNames {
84 } 85 }
85 ] 86 ]
86}) 87})
87export class VideoRedundancyModel extends Model { 88export class VideoRedundancyModel extends Model<Partial<AttributesOnly<VideoRedundancyModel>>> {
88 89
89 @CreatedAt 90 @CreatedAt
90 createdAt: Date 91 createdAt: Date
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts
index 82387af6a..8bb9649da 100644
--- a/server/models/server/plugin.ts
+++ b/server/models/server/plugin.ts
@@ -1,9 +1,8 @@
1import { FindAndCountOptions, json, QueryTypes } from 'sequelize' 1import { FindAndCountOptions, json, QueryTypes } from 'sequelize'
2import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { MPlugin, MPluginFormattable } from '@server/types/models' 3import { MPlugin, MPluginFormattable } from '@server/types/models'
4import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model' 4import { AttributesOnly } from '@shared/core-utils'
5import { PluginType } from '../../../shared/models/plugins/plugin.type' 5import { PeerTubePlugin, PluginType, RegisterServerSettingOptions } from '../../../shared/models'
6import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model'
7import { 6import {
8 isPluginDescriptionValid, 7 isPluginDescriptionValid,
9 isPluginHomepage, 8 isPluginHomepage,
@@ -28,7 +27,7 @@ import { getSort, throwIfNotValid } from '../utils'
28 } 27 }
29 ] 28 ]
30}) 29})
31export class PluginModel extends Model { 30export class PluginModel extends Model<Partial<AttributesOnly<PluginModel>>> {
32 31
33 @AllowNull(false) 32 @AllowNull(false)
34 @Is('PluginName', value => throwIfNotValid(value, isPluginNameValid, 'name')) 33 @Is('PluginName', value => throwIfNotValid(value, isPluginNameValid, 'name'))
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts
index 4dc236537..b3579d589 100644
--- a/server/models/server/server-blocklist.ts
+++ b/server/models/server/server-blocklist.ts
@@ -1,6 +1,7 @@
1import { Op } from 'sequelize' 1import { Op } from 'sequelize'
2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models' 3import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils'
4import { ServerBlock } from '@shared/models' 5import { ServerBlock } from '@shared/models'
5import { AccountModel } from '../account/account' 6import { AccountModel } from '../account/account'
6import { getSort, searchAttribute } from '../utils' 7import { getSort, searchAttribute } from '../utils'
@@ -42,7 +43,7 @@ enum ScopeNames {
42 } 43 }
43 ] 44 ]
44}) 45})
45export class ServerBlocklistModel extends Model { 46export class ServerBlocklistModel extends Model<Partial<AttributesOnly<ServerBlocklistModel>>> {
46 47
47 @CreatedAt 48 @CreatedAt
48 createdAt: Date 49 createdAt: Date
diff --git a/server/models/server/server.ts b/server/models/server/server.ts
index 0e58beeaf..25d9924fb 100644
--- a/server/models/server/server.ts
+++ b/server/models/server/server.ts
@@ -1,7 +1,8 @@
1import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { MServer, MServerFormattable } from '@server/types/models/server' 2import { MServer, MServerFormattable } from '@server/types/models/server'
3import { AttributesOnly } from '@shared/core-utils'
3import { isHostValid } from '../../helpers/custom-validators/servers' 4import { isHostValid } from '../../helpers/custom-validators/servers'
4import { ActorModel } from '../activitypub/actor' 5import { ActorModel } from '../actor/actor'
5import { throwIfNotValid } from '../utils' 6import { throwIfNotValid } from '../utils'
6import { ServerBlocklistModel } from './server-blocklist' 7import { ServerBlocklistModel } from './server-blocklist'
7 8
@@ -14,7 +15,7 @@ import { ServerBlocklistModel } from './server-blocklist'
14 } 15 }
15 ] 16 ]
16}) 17})
17export class ServerModel extends Model { 18export class ServerModel extends Model<Partial<AttributesOnly<ServerModel>>> {
18 19
19 @AllowNull(false) 20 @AllowNull(false)
20 @Is('Host', value => throwIfNotValid(value, isHostValid, 'valid host')) 21 @Is('Host', value => throwIfNotValid(value, isHostValid, 'valid host'))
diff --git a/server/models/server/tracker.ts b/server/models/server/tracker.ts
index 97520f92d..c09fdd64b 100644
--- a/server/models/server/tracker.ts
+++ b/server/models/server/tracker.ts
@@ -1,6 +1,7 @@
1import { AllowNull, BelongsToMany, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsToMany, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { Transaction } from 'sequelize/types' 2import { Transaction } from 'sequelize/types'
3import { MTracker } from '@server/types/models/server/tracker' 3import { MTracker } from '@server/types/models/server/tracker'
4import { AttributesOnly } from '@shared/core-utils'
4import { VideoModel } from '../video/video' 5import { VideoModel } from '../video/video'
5import { VideoTrackerModel } from './video-tracker' 6import { VideoTrackerModel } from './video-tracker'
6 7
@@ -13,7 +14,7 @@ import { VideoTrackerModel } from './video-tracker'
13 } 14 }
14 ] 15 ]
15}) 16})
16export class TrackerModel extends Model { 17export class TrackerModel extends Model<Partial<AttributesOnly<TrackerModel>>> {
17 18
18 @AllowNull(false) 19 @AllowNull(false)
19 @Column 20 @Column
diff --git a/server/models/server/video-tracker.ts b/server/models/server/video-tracker.ts
index 367bf0117..c49fbd1c6 100644
--- a/server/models/server/video-tracker.ts
+++ b/server/models/server/video-tracker.ts
@@ -1,4 +1,5 @@
1import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { AttributesOnly } from '@shared/core-utils'
2import { VideoModel } from '../video/video' 3import { VideoModel } from '../video/video'
3import { TrackerModel } from './tracker' 4import { TrackerModel } from './tracker'
4 5
@@ -13,7 +14,7 @@ import { TrackerModel } from './tracker'
13 } 14 }
14 ] 15 ]
15}) 16})
16export class VideoTrackerModel extends Model { 17export class VideoTrackerModel extends Model<Partial<AttributesOnly<VideoTrackerModel>>> {
17 @CreatedAt 18 @CreatedAt
18 createdAt: Date 19 createdAt: Date
19 20
diff --git a/server/models/account/user-notification-setting.ts b/server/models/user/user-notification-setting.ts
index 138051528..bee7d7851 100644
--- a/server/models/account/user-notification-setting.ts
+++ b/server/models/user/user-notification-setting.ts
@@ -14,6 +14,7 @@ import {
14} from 'sequelize-typescript' 14} from 'sequelize-typescript'
15import { TokensCache } from '@server/lib/auth/tokens-cache' 15import { TokensCache } from '@server/lib/auth/tokens-cache'
16import { MNotificationSettingFormattable } from '@server/types/models' 16import { MNotificationSettingFormattable } from '@server/types/models'
17import { AttributesOnly } from '@shared/core-utils'
17import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' 18import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
18import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' 19import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
19import { throwIfNotValid } from '../utils' 20import { throwIfNotValid } from '../utils'
@@ -28,7 +29,7 @@ import { UserModel } from './user'
28 } 29 }
29 ] 30 ]
30}) 31})
31export class UserNotificationSettingModel extends Model { 32export class UserNotificationSettingModel extends Model<Partial<AttributesOnly<UserNotificationSettingModel>>> {
32 33
33 @AllowNull(false) 34 @AllowNull(false)
34 @Default(null) 35 @Default(null)
diff --git a/server/models/account/user-notification.ts b/server/models/user/user-notification.ts
index 805095002..a7f84e9ca 100644
--- a/server/models/account/user-notification.ts
+++ b/server/models/user/user-notification.ts
@@ -1,14 +1,17 @@
1import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' 1import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' 3import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user'
4import { AttributesOnly } from '@shared/core-utils'
4import { UserNotification, UserNotificationType } from '../../../shared' 5import { UserNotification, UserNotificationType } from '../../../shared'
5import { isBooleanValid } from '../../helpers/custom-validators/misc' 6import { isBooleanValid } from '../../helpers/custom-validators/misc'
6import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' 7import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications'
7import { AbuseModel } from '../abuse/abuse' 8import { AbuseModel } from '../abuse/abuse'
8import { VideoAbuseModel } from '../abuse/video-abuse' 9import { VideoAbuseModel } from '../abuse/video-abuse'
9import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' 10import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
10import { ActorModel } from '../activitypub/actor' 11import { AccountModel } from '../account/account'
11import { ActorFollowModel } from '../activitypub/actor-follow' 12import { ActorModel } from '../actor/actor'
13import { ActorFollowModel } from '../actor/actor-follow'
14import { ActorImageModel } from '../actor/actor-image'
12import { ApplicationModel } from '../application/application' 15import { ApplicationModel } from '../application/application'
13import { PluginModel } from '../server/plugin' 16import { PluginModel } from '../server/plugin'
14import { ServerModel } from '../server/server' 17import { ServerModel } from '../server/server'
@@ -18,8 +21,6 @@ import { VideoBlacklistModel } from '../video/video-blacklist'
18import { VideoChannelModel } from '../video/video-channel' 21import { VideoChannelModel } from '../video/video-channel'
19import { VideoCommentModel } from '../video/video-comment' 22import { VideoCommentModel } from '../video/video-comment'
20import { VideoImportModel } from '../video/video-import' 23import { VideoImportModel } from '../video/video-import'
21import { AccountModel } from './account'
22import { ActorImageModel } from './actor-image'
23import { UserModel } from './user' 24import { UserModel } from './user'
24 25
25enum ScopeNames { 26enum ScopeNames {
@@ -286,7 +287,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
286 } 287 }
287 ] as (ModelIndexesOptions & { where?: WhereOptions })[] 288 ] as (ModelIndexesOptions & { where?: WhereOptions })[]
288}) 289})
289export class UserNotificationModel extends Model { 290export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNotificationModel>>> {
290 291
291 @AllowNull(false) 292 @AllowNull(false)
292 @Default(null) 293 @Default(null)
diff --git a/server/models/account/user-video-history.ts b/server/models/user/user-video-history.ts
index 6be1d65ea..e3dc4a062 100644
--- a/server/models/account/user-video-history.ts
+++ b/server/models/user/user-video-history.ts
@@ -1,8 +1,9 @@
1import { DestroyOptions, Op, Transaction } from 'sequelize'
1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { MUserAccountId, MUserId } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils'
2import { VideoModel } from '../video/video' 5import { VideoModel } from '../video/video'
3import { UserModel } from './user' 6import { UserModel } from './user'
4import { DestroyOptions, Op, Transaction } from 'sequelize'
5import { MUserAccountId, MUserId } from '@server/types/models'
6 7
7@Table({ 8@Table({
8 tableName: 'userVideoHistory', 9 tableName: 'userVideoHistory',
@@ -19,7 +20,7 @@ import { MUserAccountId, MUserId } from '@server/types/models'
19 } 20 }
20 ] 21 ]
21}) 22})
22export class UserVideoHistoryModel extends Model { 23export class UserVideoHistoryModel extends Model<Partial<AttributesOnly<UserVideoHistoryModel>>> {
23 @CreatedAt 24 @CreatedAt
24 createdAt: Date 25 createdAt: Date
25 26
diff --git a/server/models/account/user.ts b/server/models/user/user.ts
index 513455773..20696b1f4 100644
--- a/server/models/account/user.ts
+++ b/server/models/user/user.ts
@@ -31,6 +31,7 @@ import {
31 MUserWithNotificationSetting, 31 MUserWithNotificationSetting,
32 MVideoWithRights 32 MVideoWithRights
33} from '@server/types/models' 33} from '@server/types/models'
34import { AttributesOnly } from '@shared/core-utils'
34import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users' 35import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users'
35import { AbuseState, MyUser, UserRight, VideoPlaylistType, VideoPrivacy } from '../../../shared/models' 36import { AbuseState, MyUser, UserRight, VideoPlaylistType, VideoPrivacy } from '../../../shared/models'
36import { User, UserRole } from '../../../shared/models/users' 37import { User, UserRole } from '../../../shared/models/users'
@@ -60,8 +61,10 @@ import {
60import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' 61import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
61import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants' 62import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants'
62import { getThemeOrDefault } from '../../lib/plugins/theme-utils' 63import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
63import { ActorModel } from '../activitypub/actor' 64import { AccountModel } from '../account/account'
64import { ActorFollowModel } from '../activitypub/actor-follow' 65import { ActorModel } from '../actor/actor'
66import { ActorFollowModel } from '../actor/actor-follow'
67import { ActorImageModel } from '../actor/actor-image'
65import { OAuthTokenModel } from '../oauth/oauth-token' 68import { OAuthTokenModel } from '../oauth/oauth-token'
66import { getSort, throwIfNotValid } from '../utils' 69import { getSort, throwIfNotValid } from '../utils'
67import { VideoModel } from '../video/video' 70import { VideoModel } from '../video/video'
@@ -69,9 +72,7 @@ import { VideoChannelModel } from '../video/video-channel'
69import { VideoImportModel } from '../video/video-import' 72import { VideoImportModel } from '../video/video-import'
70import { VideoLiveModel } from '../video/video-live' 73import { VideoLiveModel } from '../video/video-live'
71import { VideoPlaylistModel } from '../video/video-playlist' 74import { VideoPlaylistModel } from '../video/video-playlist'
72import { AccountModel } from './account'
73import { UserNotificationSettingModel } from './user-notification-setting' 75import { UserNotificationSettingModel } from './user-notification-setting'
74import { ActorImageModel } from './actor-image'
75 76
76enum ScopeNames { 77enum ScopeNames {
77 FOR_ME_API = 'FOR_ME_API', 78 FOR_ME_API = 'FOR_ME_API',
@@ -233,7 +234,7 @@ enum ScopeNames {
233 } 234 }
234 ] 235 ]
235}) 236})
236export class UserModel extends Model { 237export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
237 238
238 @AllowNull(true) 239 @AllowNull(true)
239 @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password', true)) 240 @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password', true))
diff --git a/server/models/utils.ts b/server/models/utils.ts
index ec51c66bf..e27625bc8 100644
--- a/server/models/utils.ts
+++ b/server/models/utils.ts
@@ -1,5 +1,4 @@
1import { literal, Op, OrderItem } from 'sequelize' 1import { literal, Op, OrderItem, Sequelize } from 'sequelize'
2import { Model, Sequelize } from 'sequelize-typescript'
3import { Col } from 'sequelize/types/lib/utils' 2import { Col } from 'sequelize/types/lib/utils'
4import validator from 'validator' 3import validator from 'validator'
5 4
@@ -195,11 +194,11 @@ function parseAggregateResult (result: any) {
195 return total 194 return total
196} 195}
197 196
198const createSafeIn = (model: typeof Model, stringArr: (string | number)[]) => { 197function createSafeIn (sequelize: Sequelize, stringArr: (string | number)[]) {
199 return stringArr.map(t => { 198 return stringArr.map(t => {
200 return t === null 199 return t === null
201 ? null 200 ? null
202 : model.sequelize.escape('' + t) 201 : sequelize.escape('' + t)
203 }).join(', ') 202 }).join(', ')
204} 203}
205 204
diff --git a/server/models/video/schedule-video-update.ts b/server/models/video/schedule-video-update.ts
index 22b08e91a..b0952c431 100644
--- a/server/models/video/schedule-video-update.ts
+++ b/server/models/video/schedule-video-update.ts
@@ -1,8 +1,9 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { ScopeNames as VideoScopeNames, VideoModel } from './video'
3import { VideoPrivacy } from '../../../shared/models/videos'
4import { Op, Transaction } from 'sequelize' 1import { Op, Transaction } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
5import { MScheduleVideoUpdateFormattable, MScheduleVideoUpdateVideoAll } from '@server/types/models' 3import { MScheduleVideoUpdateFormattable, MScheduleVideoUpdateVideoAll } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils'
5import { VideoPrivacy } from '../../../shared/models/videos'
6import { ScopeNames as VideoScopeNames, VideoModel } from './video'
6 7
7@Table({ 8@Table({
8 tableName: 'scheduleVideoUpdate', 9 tableName: 'scheduleVideoUpdate',
@@ -16,7 +17,7 @@ import { MScheduleVideoUpdateFormattable, MScheduleVideoUpdateVideoAll } from '@
16 } 17 }
17 ] 18 ]
18}) 19})
19export class ScheduleVideoUpdateModel extends Model { 20export class ScheduleVideoUpdateModel extends Model<Partial<AttributesOnly<ScheduleVideoUpdateModel>>> {
20 21
21 @AllowNull(false) 22 @AllowNull(false)
22 @Default(null) 23 @Default(null)
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts
index d04205703..c1eebe27f 100644
--- a/server/models/video/tag.ts
+++ b/server/models/video/tag.ts
@@ -1,6 +1,7 @@
1import { col, fn, QueryTypes, Transaction } from 'sequelize' 1import { col, fn, QueryTypes, Transaction } from 'sequelize'
2import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { MTag } from '@server/types/models' 3import { MTag } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils'
4import { VideoPrivacy, VideoState } from '../../../shared/models/videos' 5import { VideoPrivacy, VideoState } from '../../../shared/models/videos'
5import { isVideoTagValid } from '../../helpers/custom-validators/videos' 6import { isVideoTagValid } from '../../helpers/custom-validators/videos'
6import { throwIfNotValid } from '../utils' 7import { throwIfNotValid } from '../utils'
@@ -21,7 +22,7 @@ import { VideoTagModel } from './video-tag'
21 } 22 }
22 ] 23 ]
23}) 24})
24export class TagModel extends Model { 25export class TagModel extends Model<Partial<AttributesOnly<TagModel>>> {
25 26
26 @AllowNull(false) 27 @AllowNull(false)
27 @Is('VideoTag', value => throwIfNotValid(value, isVideoTagValid, 'tag')) 28 @Is('VideoTag', value => throwIfNotValid(value, isVideoTagValid, 'tag'))
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts
index f1187c8d6..3388478d9 100644
--- a/server/models/video/thumbnail.ts
+++ b/server/models/video/thumbnail.ts
@@ -17,6 +17,7 @@ import {
17} from 'sequelize-typescript' 17} from 'sequelize-typescript'
18import { afterCommitIfTransaction } from '@server/helpers/database-utils' 18import { afterCommitIfTransaction } from '@server/helpers/database-utils'
19import { MThumbnail, MThumbnailVideo, MVideo } from '@server/types/models' 19import { MThumbnail, MThumbnailVideo, MVideo } from '@server/types/models'
20import { AttributesOnly } from '@shared/core-utils'
20import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' 21import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
21import { logger } from '../../helpers/logger' 22import { logger } from '../../helpers/logger'
22import { CONFIG } from '../../initializers/config' 23import { CONFIG } from '../../initializers/config'
@@ -40,7 +41,7 @@ import { VideoPlaylistModel } from './video-playlist'
40 } 41 }
41 ] 42 ]
42}) 43})
43export class ThumbnailModel extends Model { 44export class ThumbnailModel extends Model<Partial<AttributesOnly<ThumbnailModel>>> {
44 45
45 @AllowNull(false) 46 @AllowNull(false)
46 @Column 47 @Column
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts
index aa18896da..98f4ec9c5 100644
--- a/server/models/video/video-blacklist.ts
+++ b/server/models/video/video-blacklist.ts
@@ -1,6 +1,7 @@
1import { FindOptions } from 'sequelize' 1import { FindOptions } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { MVideoBlacklist, MVideoBlacklistFormattable } from '@server/types/models' 3import { MVideoBlacklist, MVideoBlacklistFormattable } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils'
4import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' 5import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos'
5import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' 6import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist'
6import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 7import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
@@ -18,7 +19,7 @@ import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel
18 } 19 }
19 ] 20 ]
20}) 21})
21export class VideoBlacklistModel extends Model { 22export class VideoBlacklistModel extends Model<Partial<AttributesOnly<VideoBlacklistModel>>> {
22 23
23 @AllowNull(true) 24 @AllowNull(true)
24 @Is('VideoBlacklistReason', value => throwIfNotValid(value, isVideoBlacklistReasonValid, 'reason', true)) 25 @Is('VideoBlacklistReason', value => throwIfNotValid(value, isVideoBlacklistReasonValid, 'reason', true))
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts
index bfdec73e9..d2c742b66 100644
--- a/server/models/video/video-caption.ts
+++ b/server/models/video/video-caption.ts
@@ -17,6 +17,7 @@ import {
17} from 'sequelize-typescript' 17} from 'sequelize-typescript'
18import { v4 as uuidv4 } from 'uuid' 18import { v4 as uuidv4 } from 'uuid'
19import { MVideo, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models' 19import { MVideo, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models'
20import { AttributesOnly } from '@shared/core-utils'
20import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' 21import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
21import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' 22import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
22import { logger } from '../../helpers/logger' 23import { logger } from '../../helpers/logger'
@@ -57,7 +58,7 @@ export enum ScopeNames {
57 } 58 }
58 ] 59 ]
59}) 60})
60export class VideoCaptionModel extends Model { 61export class VideoCaptionModel extends Model<Partial<AttributesOnly<VideoCaptionModel>>> {
61 @CreatedAt 62 @CreatedAt
62 createdAt: Date 63 createdAt: Date
63 64
diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts
index 298e8bfe2..7d20a954d 100644
--- a/server/models/video/video-change-ownership.ts
+++ b/server/models/video/video-change-ownership.ts
@@ -1,5 +1,6 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
2import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@server/types/models/video/video-change-ownership' 2import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@server/types/models/video/video-change-ownership'
3import { AttributesOnly } from '@shared/core-utils'
3import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' 4import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos'
4import { AccountModel } from '../account/account' 5import { AccountModel } from '../account/account'
5import { getSort } from '../utils' 6import { getSort } from '../utils'
@@ -53,7 +54,7 @@ enum ScopeNames {
53 ] 54 ]
54 } 55 }
55})) 56}))
56export class VideoChangeOwnershipModel extends Model { 57export class VideoChangeOwnershipModel extends Model<Partial<AttributesOnly<VideoChangeOwnershipModel>>> {
57 @CreatedAt 58 @CreatedAt
58 createdAt: Date 59 createdAt: Date
59 60
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index 081b21f2d..8c4357009 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -19,6 +19,7 @@ import {
19} from 'sequelize-typescript' 19} from 'sequelize-typescript'
20import { setAsUpdated } from '@server/helpers/database-utils' 20import { setAsUpdated } from '@server/helpers/database-utils'
21import { MAccountActor } from '@server/types/models' 21import { MAccountActor } from '@server/types/models'
22import { AttributesOnly } from '@shared/core-utils'
22import { ActivityPubActor } from '../../../shared/models/activitypub' 23import { ActivityPubActor } from '../../../shared/models/activitypub'
23import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos' 24import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos'
24import { 25import {
@@ -36,9 +37,9 @@ import {
36 MChannelSummaryFormattable 37 MChannelSummaryFormattable
37} from '../../types/models/video' 38} from '../../types/models/video'
38import { AccountModel, ScopeNames as AccountModelScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' 39import { AccountModel, ScopeNames as AccountModelScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account'
39import { ActorImageModel } from '../account/actor-image' 40import { ActorModel, unusedActorAttributesForAPI } from '../actor/actor'
40import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' 41import { ActorFollowModel } from '../actor/actor-follow'
41import { ActorFollowModel } from '../activitypub/actor-follow' 42import { ActorImageModel } from '../actor/actor-image'
42import { ServerModel } from '../server/server' 43import { ServerModel } from '../server/server'
43import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' 44import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils'
44import { VideoModel } from './video' 45import { VideoModel } from './video'
@@ -246,7 +247,7 @@ export type SummaryOptions = {
246 } 247 }
247 ] 248 ]
248}) 249})
249export class VideoChannelModel extends Model { 250export class VideoChannelModel extends Model<Partial<AttributesOnly<VideoChannelModel>>> {
250 251
251 @AllowNull(false) 252 @AllowNull(false)
252 @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name')) 253 @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name'))
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts
index 151c2bc81..bdf5d86bc 100644
--- a/server/models/video/video-comment.ts
+++ b/server/models/video/video-comment.ts
@@ -16,10 +16,11 @@ import {
16} from 'sequelize-typescript' 16} from 'sequelize-typescript'
17import { getServerActor } from '@server/models/application/application' 17import { getServerActor } from '@server/models/application/application'
18import { MAccount, MAccountId, MUserAccountId } from '@server/types/models' 18import { MAccount, MAccountId, MUserAccountId } from '@server/types/models'
19import { AttributesOnly } from '@shared/core-utils'
19import { VideoPrivacy } from '@shared/models' 20import { VideoPrivacy } from '@shared/models'
20import { ActivityTagObject, ActivityTombstoneObject } from '../../../shared/models/activitypub/objects/common-objects' 21import { ActivityTagObject, ActivityTombstoneObject } from '../../../shared/models/activitypub/objects/common-objects'
21import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' 22import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object'
22import { VideoComment, VideoCommentAdmin } from '../../../shared/models/videos/video-comment.model' 23import { VideoComment, VideoCommentAdmin } from '../../../shared/models/videos/comment/video-comment.model'
23import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' 24import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
24import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 25import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
25import { regexpCapture } from '../../helpers/regexp' 26import { regexpCapture } from '../../helpers/regexp'
@@ -39,7 +40,7 @@ import {
39} from '../../types/models/video' 40} from '../../types/models/video'
40import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' 41import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
41import { AccountModel } from '../account/account' 42import { AccountModel } from '../account/account'
42import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' 43import { ActorModel, unusedActorAttributesForAPI } from '../actor/actor'
43import { 44import {
44 buildBlockedAccountSQL, 45 buildBlockedAccountSQL,
45 buildBlockedAccountSQLOptimized, 46 buildBlockedAccountSQLOptimized,
@@ -173,7 +174,7 @@ export enum ScopeNames {
173 } 174 }
174 ] 175 ]
175}) 176})
176export class VideoCommentModel extends Model { 177export class VideoCommentModel extends Model<Partial<AttributesOnly<VideoCommentModel>>> {
177 @CreatedAt 178 @CreatedAt
178 createdAt: Date 179 createdAt: Date
179 180
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts
index 1ad796104..e3fa2f3d2 100644
--- a/server/models/video/video-file.ts
+++ b/server/models/video/video-file.ts
@@ -25,6 +25,7 @@ import { logger } from '@server/helpers/logger'
25import { extractVideo } from '@server/helpers/video' 25import { extractVideo } from '@server/helpers/video'
26import { getTorrentFilePath } from '@server/lib/video-paths' 26import { getTorrentFilePath } from '@server/lib/video-paths'
27import { MStreamingPlaylistVideo, MVideo, MVideoWithHost } from '@server/types/models' 27import { MStreamingPlaylistVideo, MVideo, MVideoWithHost } from '@server/types/models'
28import { AttributesOnly } from '@shared/core-utils'
28import { 29import {
29 isVideoFileExtnameValid, 30 isVideoFileExtnameValid,
30 isVideoFileInfoHashValid, 31 isVideoFileInfoHashValid,
@@ -149,7 +150,7 @@ export enum ScopeNames {
149 } 150 }
150 ] 151 ]
151}) 152})
152export class VideoFileModel extends Model { 153export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>>> {
153 @CreatedAt 154 @CreatedAt
154 createdAt: Date 155 createdAt: Date
155 156
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts
index 8324166cc..5c73fb07c 100644
--- a/server/models/video/video-import.ts
+++ b/server/models/video/video-import.ts
@@ -13,15 +13,16 @@ import {
13 Table, 13 Table,
14 UpdatedAt 14 UpdatedAt
15} from 'sequelize-typescript' 15} from 'sequelize-typescript'
16import { afterCommitIfTransaction } from '@server/helpers/database-utils'
16import { MVideoImportDefault, MVideoImportFormattable } from '@server/types/models/video/video-import' 17import { MVideoImportDefault, MVideoImportFormattable } from '@server/types/models/video/video-import'
18import { AttributesOnly } from '@shared/core-utils'
17import { VideoImport, VideoImportState } from '../../../shared' 19import { VideoImport, VideoImportState } from '../../../shared'
18import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports' 20import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports'
19import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' 21import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
20import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants' 22import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants'
21import { UserModel } from '../account/user' 23import { UserModel } from '../user/user'
22import { getSort, throwIfNotValid } from '../utils' 24import { getSort, throwIfNotValid } from '../utils'
23import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' 25import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
24import { afterCommitIfTransaction } from '@server/helpers/database-utils'
25 26
26@DefaultScope(() => ({ 27@DefaultScope(() => ({
27 include: [ 28 include: [
@@ -52,7 +53,7 @@ import { afterCommitIfTransaction } from '@server/helpers/database-utils'
52 } 53 }
53 ] 54 ]
54}) 55})
55export class VideoImportModel extends Model { 56export class VideoImportModel extends Model<Partial<AttributesOnly<VideoImportModel>>> {
56 @CreatedAt 57 @CreatedAt
57 createdAt: Date 58 createdAt: Date
58 59
diff --git a/server/models/video/video-live.ts b/server/models/video/video-live.ts
index cb4a9b896..014491d50 100644
--- a/server/models/video/video-live.ts
+++ b/server/models/video/video-live.ts
@@ -1,6 +1,7 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, DefaultScope, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, DefaultScope, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { WEBSERVER } from '@server/initializers/constants' 2import { WEBSERVER } from '@server/initializers/constants'
3import { MVideoLive, MVideoLiveVideo } from '@server/types/models' 3import { MVideoLive, MVideoLiveVideo } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils'
4import { LiveVideo, VideoState } from '@shared/models' 5import { LiveVideo, VideoState } from '@shared/models'
5import { VideoModel } from './video' 6import { VideoModel } from './video'
6import { VideoBlacklistModel } from './video-blacklist' 7import { VideoBlacklistModel } from './video-blacklist'
@@ -28,7 +29,7 @@ import { VideoBlacklistModel } from './video-blacklist'
28 } 29 }
29 ] 30 ]
30}) 31})
31export class VideoLiveModel extends Model { 32export class VideoLiveModel extends Model<Partial<AttributesOnly<VideoLiveModel>>> {
32 33
33 @AllowNull(true) 34 @AllowNull(true)
34 @Column(DataType.STRING) 35 @Column(DataType.STRING)
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts
index d2d7e2740..e6906cb19 100644
--- a/server/models/video/video-playlist-element.ts
+++ b/server/models/video/video-playlist-element.ts
@@ -32,6 +32,7 @@ import { AccountModel } from '../account/account'
32import { getSort, throwIfNotValid } from '../utils' 32import { getSort, throwIfNotValid } from '../utils'
33import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video' 33import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video'
34import { VideoPlaylistModel } from './video-playlist' 34import { VideoPlaylistModel } from './video-playlist'
35import { AttributesOnly } from '@shared/core-utils'
35 36
36@Table({ 37@Table({
37 tableName: 'videoPlaylistElement', 38 tableName: 'videoPlaylistElement',
@@ -48,7 +49,7 @@ import { VideoPlaylistModel } from './video-playlist'
48 } 49 }
49 ] 50 ]
50}) 51})
51export class VideoPlaylistElementModel extends Model { 52export class VideoPlaylistElementModel extends Model<Partial<AttributesOnly<VideoPlaylistElementModel>>> {
52 @CreatedAt 53 @CreatedAt
53 createdAt: Date 54 createdAt: Date
54 55
@@ -274,7 +275,8 @@ export class VideoPlaylistElementModel extends Model {
274 validate: false // We use a literal to update the position 275 validate: false // We use a literal to update the position
275 } 276 }
276 277
277 return VideoPlaylistElementModel.update({ position: Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`) }, query) 278 const positionQuery = Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`)
279 return VideoPlaylistElementModel.update({ position: positionQuery as any }, query)
278 } 280 }
279 281
280 static increasePositionOf ( 282 static increasePositionOf (
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index efe5be36d..c293287d3 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -19,6 +19,7 @@ import {
19} from 'sequelize-typescript' 19} from 'sequelize-typescript'
20import { v4 as uuidv4 } from 'uuid' 20import { v4 as uuidv4 } from 'uuid'
21import { MAccountId, MChannelId } from '@server/types/models' 21import { MAccountId, MChannelId } from '@server/types/models'
22import { AttributesOnly } from '@shared/core-utils'
22import { ActivityIconObject } from '../../../shared/models/activitypub/objects' 23import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
23import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' 24import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
24import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' 25import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
@@ -50,11 +51,11 @@ import {
50 MVideoPlaylistIdWithElements 51 MVideoPlaylistIdWithElements
51} from '../../types/models/video/video-playlist' 52} from '../../types/models/video/video-playlist'
52import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account' 53import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account'
54import { ActorModel } from '../actor/actor'
53import { buildServerIdsFollowedBy, buildWhereIdOrUUID, getPlaylistSort, isOutdated, throwIfNotValid } from '../utils' 55import { buildServerIdsFollowedBy, buildWhereIdOrUUID, getPlaylistSort, isOutdated, throwIfNotValid } from '../utils'
54import { ThumbnailModel } from './thumbnail' 56import { ThumbnailModel } from './thumbnail'
55import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' 57import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel'
56import { VideoPlaylistElementModel } from './video-playlist-element' 58import { VideoPlaylistElementModel } from './video-playlist-element'
57import { ActorModel } from '../activitypub/actor'
58 59
59enum ScopeNames { 60enum ScopeNames {
60 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', 61 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
@@ -221,7 +222,7 @@ type AvailableForListOptions = {
221 } 222 }
222 ] 223 ]
223}) 224})
224export class VideoPlaylistModel extends Model { 225export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlaylistModel>>> {
225 @CreatedAt 226 @CreatedAt
226 createdAt: Date 227 createdAt: Date
227 228
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts
index 155afe64b..2aa5e65c8 100644
--- a/server/models/video/video-query-builder.ts
+++ b/server/models/video/video-query-builder.ts
@@ -1,9 +1,9 @@
1import { VideoFilter, VideoPrivacy, VideoState } from '@shared/models' 1import { Sequelize } from 'sequelize/types'
2import { buildDirectionAndField, createSafeIn } from '@server/models/utils'
3import { Model } from 'sequelize-typescript'
4import { MUserAccountId, MUserId } from '@server/types/models'
5import validator from 'validator' 2import validator from 'validator'
6import { exists } from '@server/helpers/custom-validators/misc' 3import { exists } from '@server/helpers/custom-validators/misc'
4import { buildDirectionAndField, createSafeIn } from '@server/models/utils'
5import { MUserAccountId, MUserId } from '@server/types/models'
6import { VideoFilter, VideoPrivacy, VideoState } from '@shared/models'
7 7
8export type BuildVideosQueryOptions = { 8export type BuildVideosQueryOptions = {
9 attributes?: string[] 9 attributes?: string[]
@@ -55,7 +55,7 @@ export type BuildVideosQueryOptions = {
55 having?: string 55 having?: string
56} 56}
57 57
58function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions) { 58function buildListQuery (sequelize: Sequelize, options: BuildVideosQueryOptions) {
59 const and: string[] = [] 59 const and: string[] = []
60 const joins: string[] = [] 60 const joins: string[] = []
61 const replacements: any = {} 61 const replacements: any = {}
@@ -77,7 +77,7 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions)
77 const blockerIds = [ options.serverAccountId ] 77 const blockerIds = [ options.serverAccountId ]
78 if (options.user) blockerIds.push(options.user.Account.id) 78 if (options.user) blockerIds.push(options.user.Account.id)
79 79
80 const inClause = createSafeIn(model, blockerIds) 80 const inClause = createSafeIn(sequelize, blockerIds)
81 81
82 and.push( 82 and.push(
83 'NOT EXISTS (' + 83 'NOT EXISTS (' +
@@ -179,7 +179,7 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions)
179 'EXISTS (' + 179 'EXISTS (' +
180 ' SELECT 1 FROM "videoTag" ' + 180 ' SELECT 1 FROM "videoTag" ' +
181 ' INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 181 ' INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
182 ' WHERE lower("tag"."name") IN (' + createSafeIn(model, tagsOneOfLower) + ') ' + 182 ' WHERE lower("tag"."name") IN (' + createSafeIn(sequelize, tagsOneOfLower) + ') ' +
183 ' AND "video"."id" = "videoTag"."videoId"' + 183 ' AND "video"."id" = "videoTag"."videoId"' +
184 ')' 184 ')'
185 ) 185 )
@@ -192,7 +192,7 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions)
192 'EXISTS (' + 192 'EXISTS (' +
193 ' SELECT 1 FROM "videoTag" ' + 193 ' SELECT 1 FROM "videoTag" ' +
194 ' INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 194 ' INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
195 ' WHERE lower("tag"."name") IN (' + createSafeIn(model, tagsAllOfLower) + ') ' + 195 ' WHERE lower("tag"."name") IN (' + createSafeIn(sequelize, tagsAllOfLower) + ') ' +
196 ' AND "video"."id" = "videoTag"."videoId" ' + 196 ' AND "video"."id" = "videoTag"."videoId" ' +
197 ' GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + tagsAllOfLower.length + 197 ' GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + tagsAllOfLower.length +
198 ')' 198 ')'
@@ -232,7 +232,7 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions)
232 languagesQueryParts.push( 232 languagesQueryParts.push(
233 'EXISTS (' + 233 'EXISTS (' +
234 ' SELECT 1 FROM "videoCaption" WHERE "videoCaption"."language" ' + 234 ' SELECT 1 FROM "videoCaption" WHERE "videoCaption"."language" ' +
235 ' IN (' + createSafeIn(model, languages) + ') AND ' + 235 ' IN (' + createSafeIn(sequelize, languages) + ') AND ' +
236 ' "videoCaption"."videoId" = "video"."id"' + 236 ' "videoCaption"."videoId" = "video"."id"' +
237 ')' 237 ')'
238 ) 238 )
@@ -345,8 +345,8 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions)
345 } 345 }
346 346
347 if (options.search) { 347 if (options.search) {
348 const escapedSearch = model.sequelize.escape(options.search) 348 const escapedSearch = sequelize.escape(options.search)
349 const escapedLikeSearch = model.sequelize.escape('%' + options.search + '%') 349 const escapedLikeSearch = sequelize.escape('%' + options.search + '%')
350 350
351 cte.push( 351 cte.push(
352 '"trigramSearch" AS (' + 352 '"trigramSearch" AS (' +
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts
index 5059c1fa6..505c305e2 100644
--- a/server/models/video/video-share.ts
+++ b/server/models/video/video-share.ts
@@ -1,10 +1,11 @@
1import { literal, Op, QueryTypes, Transaction } from 'sequelize' 1import { literal, Op, QueryTypes, Transaction } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { AttributesOnly } from '@shared/core-utils'
3import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 4import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
4import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 5import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
5import { MActorDefault } from '../../types/models' 6import { MActorDefault } from '../../types/models'
6import { MVideoShareActor, MVideoShareFull } from '../../types/models/video' 7import { MVideoShareActor, MVideoShareFull } from '../../types/models/video'
7import { ActorModel } from '../activitypub/actor' 8import { ActorModel } from '../actor/actor'
8import { buildLocalActorIdsIn, throwIfNotValid } from '../utils' 9import { buildLocalActorIdsIn, throwIfNotValid } from '../utils'
9import { VideoModel } from './video' 10import { VideoModel } from './video'
10 11
@@ -50,7 +51,7 @@ enum ScopeNames {
50 } 51 }
51 ] 52 ]
52}) 53})
53export class VideoShareModel extends Model { 54export class VideoShareModel extends Model<Partial<AttributesOnly<VideoShareModel>>> {
54 55
55 @AllowNull(false) 56 @AllowNull(false)
56 @Is('VideoShareUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) 57 @Is('VideoShareUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts
index c9375b433..d627e8c9d 100644
--- a/server/models/video/video-streaming-playlist.ts
+++ b/server/models/video/video-streaming-playlist.ts
@@ -13,6 +13,7 @@ import { CONSTRAINTS_FIELDS, MEMOIZE_LENGTH, MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_
13import { VideoRedundancyModel } from '../redundancy/video-redundancy' 13import { VideoRedundancyModel } from '../redundancy/video-redundancy'
14import { throwIfNotValid } from '../utils' 14import { throwIfNotValid } from '../utils'
15import { VideoModel } from './video' 15import { VideoModel } from './video'
16import { AttributesOnly } from '@shared/core-utils'
16 17
17@Table({ 18@Table({
18 tableName: 'videoStreamingPlaylist', 19 tableName: 'videoStreamingPlaylist',
@@ -30,7 +31,7 @@ import { VideoModel } from './video'
30 } 31 }
31 ] 32 ]
32}) 33})
33export class VideoStreamingPlaylistModel extends Model { 34export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<VideoStreamingPlaylistModel>>> {
34 @CreatedAt 35 @CreatedAt
35 createdAt: Date 36 createdAt: Date
36 37
diff --git a/server/models/video/video-tag.ts b/server/models/video/video-tag.ts
index 5052b8c4d..1285d375b 100644
--- a/server/models/video/video-tag.ts
+++ b/server/models/video/video-tag.ts
@@ -1,4 +1,5 @@
1import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { AttributesOnly } from '@shared/core-utils'
2import { TagModel } from './tag' 3import { TagModel } from './tag'
3import { VideoModel } from './video' 4import { VideoModel } from './video'
4 5
@@ -13,7 +14,7 @@ import { VideoModel } from './video'
13 } 14 }
14 ] 15 ]
15}) 16})
16export class VideoTagModel extends Model { 17export class VideoTagModel extends Model<Partial<AttributesOnly<VideoTagModel>>> {
17 @CreatedAt 18 @CreatedAt
18 createdAt: Date 19 createdAt: Date
19 20
diff --git a/server/models/video/video-view.ts b/server/models/video/video-view.ts
index 992cf258a..dfc6296ce 100644
--- a/server/models/video/video-view.ts
+++ b/server/models/video/video-view.ts
@@ -1,6 +1,7 @@
1import * as Sequelize from 'sequelize'
1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Table } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Table } from 'sequelize-typescript'
3import { AttributesOnly } from '@shared/core-utils'
2import { VideoModel } from './video' 4import { VideoModel } from './video'
3import * as Sequelize from 'sequelize'
4 5
5@Table({ 6@Table({
6 tableName: 'videoView', 7 tableName: 'videoView',
@@ -14,7 +15,7 @@ import * as Sequelize from 'sequelize'
14 } 15 }
15 ] 16 ]
16}) 17})
17export class VideoViewModel extends Model { 18export class VideoViewModel extends Model<Partial<AttributesOnly<VideoViewModel>>> {
18 @CreatedAt 19 @CreatedAt
19 createdAt: Date 20 createdAt: Date
20 21
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 8c316e00c..749ef7197 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -31,6 +31,7 @@ import { LiveManager } from '@server/lib/live-manager'
31import { getHLSDirectory, getVideoFilePath } from '@server/lib/video-paths' 31import { getHLSDirectory, getVideoFilePath } from '@server/lib/video-paths'
32import { getServerActor } from '@server/models/application/application' 32import { getServerActor } from '@server/models/application/application'
33import { ModelCache } from '@server/models/model-cache' 33import { ModelCache } from '@server/models/model-cache'
34import { AttributesOnly } from '@shared/core-utils'
34import { VideoFile } from '@shared/models/videos/video-file.model' 35import { VideoFile } from '@shared/models/videos/video-file.model'
35import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared' 36import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared'
36import { VideoObject } from '../../../shared/models/activitypub/objects' 37import { VideoObject } from '../../../shared/models/activitypub/objects'
@@ -100,14 +101,14 @@ import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models
100import { VideoAbuseModel } from '../abuse/video-abuse' 101import { VideoAbuseModel } from '../abuse/video-abuse'
101import { AccountModel } from '../account/account' 102import { AccountModel } from '../account/account'
102import { AccountVideoRateModel } from '../account/account-video-rate' 103import { AccountVideoRateModel } from '../account/account-video-rate'
103import { ActorImageModel } from '../account/actor-image' 104import { ActorModel } from '../actor/actor'
104import { UserModel } from '../account/user' 105import { ActorImageModel } from '../actor/actor-image'
105import { UserVideoHistoryModel } from '../account/user-video-history'
106import { ActorModel } from '../activitypub/actor'
107import { VideoRedundancyModel } from '../redundancy/video-redundancy' 106import { VideoRedundancyModel } from '../redundancy/video-redundancy'
108import { ServerModel } from '../server/server' 107import { ServerModel } from '../server/server'
109import { TrackerModel } from '../server/tracker' 108import { TrackerModel } from '../server/tracker'
110import { VideoTrackerModel } from '../server/video-tracker' 109import { VideoTrackerModel } from '../server/video-tracker'
110import { UserModel } from '../user/user'
111import { UserVideoHistoryModel } from '../user/user-video-history'
111import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils' 112import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils'
112import { ScheduleVideoUpdateModel } from './schedule-video-update' 113import { ScheduleVideoUpdateModel } from './schedule-video-update'
113import { TagModel } from './tag' 114import { TagModel } from './tag'
@@ -489,7 +490,7 @@ export type AvailableForListIDsOptions = {
489 } 490 }
490 ] 491 ]
491}) 492})
492export class VideoModel extends Model { 493export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
493 494
494 @AllowNull(false) 495 @AllowNull(false)
495 @Default(DataType.UUIDV4) 496 @Default(DataType.UUIDV4)
@@ -1617,7 +1618,7 @@ export class VideoModel extends Model {
1617 includeLocalVideos: true 1618 includeLocalVideos: true
1618 } 1619 }
1619 1620
1620 const { query, replacements } = buildListQuery(VideoModel, queryOptions) 1621 const { query, replacements } = buildListQuery(VideoModel.sequelize, queryOptions)
1621 1622
1622 return this.sequelize.query<any>(query, { replacements, type: QueryTypes.SELECT }) 1623 return this.sequelize.query<any>(query, { replacements, type: QueryTypes.SELECT })
1623 .then(rows => rows.map(r => r[field])) 1624 .then(rows => rows.map(r => r[field]))
@@ -1645,7 +1646,7 @@ export class VideoModel extends Model {
1645 if (countVideos !== true) return Promise.resolve(undefined) 1646 if (countVideos !== true) return Promise.resolve(undefined)
1646 1647
1647 const countOptions = Object.assign({}, options, { isCount: true }) 1648 const countOptions = Object.assign({}, options, { isCount: true })
1648 const { query: queryCount, replacements: replacementsCount } = buildListQuery(VideoModel, countOptions) 1649 const { query: queryCount, replacements: replacementsCount } = buildListQuery(VideoModel.sequelize, countOptions)
1649 1650
1650 return VideoModel.sequelize.query<any>(queryCount, { replacements: replacementsCount, type: QueryTypes.SELECT }) 1651 return VideoModel.sequelize.query<any>(queryCount, { replacements: replacementsCount, type: QueryTypes.SELECT })
1651 .then(rows => rows.length !== 0 ? rows[0].total : 0) 1652 .then(rows => rows.length !== 0 ? rows[0].total : 0)
@@ -1654,7 +1655,7 @@ export class VideoModel extends Model {
1654 function getModels () { 1655 function getModels () {
1655 if (options.count === 0) return Promise.resolve([]) 1656 if (options.count === 0) return Promise.resolve([])
1656 1657
1657 const { query, replacements, order } = buildListQuery(VideoModel, options) 1658 const { query, replacements, order } = buildListQuery(VideoModel.sequelize, options)
1658 const queryModels = wrapForAPIResults(query, replacements, options, order) 1659 const queryModels = wrapForAPIResults(query, replacements, options, order)
1659 1660
1660 return VideoModel.sequelize.query<any>(queryModels, { replacements, type: QueryTypes.SELECT, nest: true }) 1661 return VideoModel.sequelize.query<any>(queryModels, { replacements, type: QueryTypes.SELECT, nest: true })
diff --git a/server/tests/api/check-params/plugins.ts b/server/tests/api/check-params/plugins.ts
index 6e540bcbb..a833fe6ff 100644
--- a/server/tests/api/check-params/plugins.ts
+++ b/server/tests/api/check-params/plugins.ts
@@ -1,7 +1,7 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4import { HttpStatusCode } from '@shared/core-utils'
5import { 5import {
6 checkBadCountPagination, 6 checkBadCountPagination,
7 checkBadSortPagination, 7 checkBadSortPagination,
@@ -11,14 +11,14 @@ import {
11 flushAndRunServer, 11 flushAndRunServer,
12 immutableAssign, 12 immutableAssign,
13 installPlugin, 13 installPlugin,
14 makeGetRequest, makePostBodyRequest, makePutBodyRequest, 14 makeGetRequest,
15 makePostBodyRequest,
16 makePutBodyRequest,
15 ServerInfo, 17 ServerInfo,
16 setAccessTokensToServers, 18 setAccessTokensToServers,
17 userLogin 19 userLogin
18} from '../../../../shared/extra-utils' 20} from '@shared/extra-utils'
19import { PluginType } from '../../../../shared/models/plugins/plugin.type' 21import { PeerTubePlugin, PluginType } from '@shared/models'
20import { PeerTubePlugin } from '../../../../shared/models/plugins/peertube-plugin.model'
21import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
22 22
23describe('Test server plugins API validators', function () { 23describe('Test server plugins API validators', function () {
24 let server: ServerInfo 24 let server: ServerInfo
diff --git a/server/tests/api/moderation/blocklist.ts b/server/tests/api/moderation/blocklist.ts
index e8202aff1..b767d38c7 100644
--- a/server/tests/api/moderation/blocklist.ts
+++ b/server/tests/api/moderation/blocklist.ts
@@ -1,46 +1,50 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai'
4import 'mocha' 3import 'mocha'
5import { AccountBlock, ServerBlock, Video, UserNotification, UserNotificationType } from '../../../../shared/index' 4import * as chai from 'chai'
6import { 5import {
6 addAccountToAccountBlocklist,
7 addAccountToServerBlocklist,
8 addServerToAccountBlocklist,
9 addServerToServerBlocklist,
10 addVideoCommentReply,
11 addVideoCommentThread,
7 cleanupTests, 12 cleanupTests,
8 createUser, 13 createUser,
9 deleteVideoComment, 14 deleteVideoComment,
10 doubleFollow, 15 doubleFollow,
16 findCommentId,
11 flushAndRunMultipleServers, 17 flushAndRunMultipleServers,
12 ServerInfo,
13 uploadVideo,
14 userLogin,
15 follow, 18 follow,
16 unfollow
17} from '../../../../shared/extra-utils/index'
18import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
19import { getVideosList, getVideosListWithToken } from '../../../../shared/extra-utils/videos/videos'
20import {
21 addVideoCommentReply,
22 addVideoCommentThread,
23 getVideoCommentThreads,
24 getVideoThreadComments,
25 findCommentId
26} from '../../../../shared/extra-utils/videos/video-comments'
27import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
28import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
29import {
30 addAccountToAccountBlocklist,
31 addAccountToServerBlocklist,
32 addServerToAccountBlocklist,
33 addServerToServerBlocklist,
34 getAccountBlocklistByAccount, 19 getAccountBlocklistByAccount,
35 getAccountBlocklistByServer, 20 getAccountBlocklistByServer,
36 getServerBlocklistByAccount, 21 getServerBlocklistByAccount,
37 getServerBlocklistByServer, 22 getServerBlocklistByServer,
23 getUserNotifications,
24 getVideoCommentThreads,
25 getVideosList,
26 getVideosListWithToken,
27 getVideoThreadComments,
38 removeAccountFromAccountBlocklist, 28 removeAccountFromAccountBlocklist,
39 removeAccountFromServerBlocklist, 29 removeAccountFromServerBlocklist,
40 removeServerFromAccountBlocklist, 30 removeServerFromAccountBlocklist,
41 removeServerFromServerBlocklist 31 removeServerFromServerBlocklist,
42} from '../../../../shared/extra-utils/users/blocklist' 32 ServerInfo,
43import { getUserNotifications } from '../../../../shared/extra-utils/users/user-notifications' 33 setAccessTokensToServers,
34 unfollow,
35 uploadVideo,
36 userLogin,
37 waitJobs
38} from '@shared/extra-utils'
39import {
40 AccountBlock,
41 ServerBlock,
42 UserNotification,
43 UserNotificationType,
44 Video,
45 VideoComment,
46 VideoCommentThreadTree
47} from '@shared/models'
44 48
45const expect = chai.expect 49const expect = chai.expect
46 50
diff --git a/server/tests/api/notifications/comments-notifications.ts b/server/tests/api/notifications/comments-notifications.ts
index 5e4ab0d6c..d2badf237 100644
--- a/server/tests/api/notifications/comments-notifications.ts
+++ b/server/tests/api/notifications/comments-notifications.ts
@@ -2,20 +2,25 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, getVideoCommentThreads, getVideoThreadComments, updateMyUser } from '../../../../shared/extra-utils'
6import { ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index'
7import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
8import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
9import { addAccountToAccountBlocklist, removeAccountFromAccountBlocklist } from '../../../../shared/extra-utils/users/blocklist'
10import { 5import {
6 addAccountToAccountBlocklist,
7 addVideoCommentReply,
8 addVideoCommentThread,
11 checkCommentMention, 9 checkCommentMention,
12 CheckerBaseParams, 10 CheckerBaseParams,
13 checkNewCommentOnMyVideo, 11 checkNewCommentOnMyVideo,
14 prepareNotificationsTest 12 cleanupTests,
15} from '../../../../shared/extra-utils/users/user-notifications' 13 getVideoCommentThreads,
16import { addVideoCommentReply, addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' 14 getVideoThreadComments,
17import { UserNotification } from '../../../../shared/models/users' 15 MockSmtpServer,
18import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' 16 prepareNotificationsTest,
17 removeAccountFromAccountBlocklist,
18 ServerInfo,
19 updateMyUser,
20 uploadVideo,
21 waitJobs
22} from '@shared/extra-utils'
23import { UserNotification, VideoCommentThreadTree } from '@shared/models'
19 24
20const expect = chai.expect 25const expect = chai.expect
21 26
diff --git a/server/tests/api/server/bulk.ts b/server/tests/api/server/bulk.ts
index 51ba0e7af..80fa7fce6 100644
--- a/server/tests/api/server/bulk.ts
+++ b/server/tests/api/server/bulk.ts
@@ -2,12 +2,14 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { VideoComment } from '@shared/models/videos/video-comment.model' 5import { Video, VideoComment } from '@shared/models'
6import { 6import {
7 addVideoCommentReply,
7 addVideoCommentThread, 8 addVideoCommentThread,
8 bulkRemoveCommentsOf, 9 bulkRemoveCommentsOf,
9 cleanupTests, 10 cleanupTests,
10 createUser, 11 createUser,
12 doubleFollow,
11 flushAndRunMultipleServers, 13 flushAndRunMultipleServers,
12 getVideoCommentThreads, 14 getVideoCommentThreads,
13 getVideosList, 15 getVideosList,
@@ -15,11 +17,8 @@ import {
15 setAccessTokensToServers, 17 setAccessTokensToServers,
16 uploadVideo, 18 uploadVideo,
17 userLogin, 19 userLogin,
18 waitJobs, 20 waitJobs
19 addVideoCommentReply
20} from '../../../../shared/extra-utils/index' 21} from '../../../../shared/extra-utils/index'
21import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
22import { Video } from '@shared/models'
23 22
24const expect = chai.expect 23const expect = chai.expect
25 24
diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts
index eb9ab10eb..e1c062020 100644
--- a/server/tests/api/server/follows.ts
+++ b/server/tests/api/server/follows.ts
@@ -1,37 +1,35 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai'
4import 'mocha' 3import 'mocha'
5import { Video, VideoPrivacy } from '../../../../shared/models/videos' 4import * as chai from 'chai'
6import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
7import { cleanupTests, completeVideoCheck, deleteVideoComment } from '../../../../shared/extra-utils'
8import { 5import {
6 addVideoCommentReply,
7 addVideoCommentThread,
8 cleanupTests,
9 completeVideoCheck,
10 createUser,
11 createVideoCaption,
12 dateIsValid,
13 deleteVideoComment,
14 expectAccountFollows,
9 flushAndRunMultipleServers, 15 flushAndRunMultipleServers,
10 getVideosList,
11 ServerInfo,
12 setAccessTokensToServers,
13 uploadVideo
14} from '../../../../shared/extra-utils/index'
15import { dateIsValid } from '../../../../shared/extra-utils/miscs/miscs'
16import {
17 follow, 16 follow,
18 getFollowersListPaginationAndSort, 17 getFollowersListPaginationAndSort,
19 getFollowingListPaginationAndSort, 18 getFollowingListPaginationAndSort,
20 unfollow
21} from '../../../../shared/extra-utils/server/follows'
22import { expectAccountFollows } from '../../../../shared/extra-utils/users/accounts'
23import { userLogin } from '../../../../shared/extra-utils/users/login'
24import { createUser } from '../../../../shared/extra-utils/users/users'
25import {
26 addVideoCommentReply,
27 addVideoCommentThread,
28 getVideoCommentThreads, 19 getVideoCommentThreads,
29 getVideoThreadComments 20 getVideosList,
30} from '../../../../shared/extra-utils/videos/video-comments' 21 getVideoThreadComments,
31import { rateVideo } from '../../../../shared/extra-utils/videos/videos' 22 listVideoCaptions,
32import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 23 rateVideo,
33import { createVideoCaption, listVideoCaptions, testCaptionFile } from '../../../../shared/extra-utils/videos/video-captions' 24 ServerInfo,
34import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' 25 setAccessTokensToServers,
26 testCaptionFile,
27 unfollow,
28 uploadVideo,
29 userLogin,
30 waitJobs
31} from '@shared/extra-utils'
32import { Video, VideoCaption, VideoComment, VideoCommentThreadTree, VideoPrivacy } from '@shared/models'
35 33
36const expect = chai.expect 34const expect = chai.expect
37 35
diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts
index 817c79f6e..fe4a0e100 100644
--- a/server/tests/api/server/handle-down.ts
+++ b/server/tests/api/server/handle-down.ts
@@ -4,7 +4,7 @@ import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { JobState, Video } from '../../../../shared/models' 5import { JobState, Video } from '../../../../shared/models'
6import { VideoPrivacy } from '../../../../shared/models/videos' 6import { VideoPrivacy } from '../../../../shared/models/videos'
7import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' 7import { VideoCommentThreadTree } from '../../../../shared/models/videos/comment/video-comment.model'
8 8
9import { 9import {
10 cleanupTests, 10 cleanupTests,
@@ -346,10 +346,12 @@ describe('Test handle downs', function () {
346 // Wait video expiration 346 // Wait video expiration
347 await wait(11000) 347 await wait(11000)
348 348
349 for (let i = 0; i < 3; i++) { 349 for (let i = 0; i < 5; i++) {
350 await getVideo(servers[1].url, videoIdsServer1[i]) 350 try {
351 await waitJobs([ servers[1] ]) 351 await getVideo(servers[1].url, videoIdsServer1[i])
352 await wait(1500) 352 await waitJobs([ servers[1] ])
353 await wait(1500)
354 } catch {}
353 } 355 }
354 356
355 for (const id of videoIdsServer1) { 357 for (const id of videoIdsServer1) {
diff --git a/server/tests/api/server/plugins.ts b/server/tests/api/server/plugins.ts
index 1c6eabe6d..3c09acc9a 100644
--- a/server/tests/api/server/plugins.ts
+++ b/server/tests/api/server/plugins.ts
@@ -28,14 +28,8 @@ import {
28 updatePluginSettings, 28 updatePluginSettings,
29 wait, 29 wait,
30 waitUntilLog 30 waitUntilLog
31} from '../../../../shared/extra-utils' 31} from '@shared/extra-utils'
32import { PeerTubePluginIndex } from '../../../../shared/models/plugins/peertube-plugin-index.model' 32import { PeerTubePlugin, PeerTubePluginIndex, PluginPackageJson, PluginType, PublicServerSetting, ServerConfig, User } from '@shared/models'
33import { PeerTubePlugin } from '../../../../shared/models/plugins/peertube-plugin.model'
34import { PluginPackageJson } from '../../../../shared/models/plugins/plugin-package-json.model'
35import { PluginType } from '../../../../shared/models/plugins/plugin.type'
36import { PublicServerSetting } from '../../../../shared/models/plugins/public-server.setting'
37import { ServerConfig } from '../../../../shared/models/server'
38import { User } from '../../../../shared/models/users'
39 33
40const expect = chai.expect 34const expect = chai.expect
41 35
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index 41cd814e0..6aa996038 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -1,11 +1,10 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai'
4import 'mocha' 3import 'mocha'
4import * as chai from 'chai'
5import { join } from 'path' 5import { join } from 'path'
6import * as request from 'supertest' 6import * as request from 'supertest'
7import { VideoPrivacy } from '../../../../shared/models/videos' 7import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
8import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
9import { 8import {
10 addVideoChannel, 9 addVideoChannel,
11 checkTmpIsEmpty, 10 checkTmpIsEmpty,
@@ -32,16 +31,16 @@ import {
32 wait, 31 wait,
33 webtorrentAdd 32 webtorrentAdd
34} from '../../../../shared/extra-utils' 33} from '../../../../shared/extra-utils'
34import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
35import { 35import {
36 addVideoCommentReply, 36 addVideoCommentReply,
37 addVideoCommentThread, 37 addVideoCommentThread,
38 deleteVideoComment, 38 deleteVideoComment,
39 findCommentId,
39 getVideoCommentThreads, 40 getVideoCommentThreads,
40 getVideoThreadComments, 41 getVideoThreadComments
41 findCommentId
42} from '../../../../shared/extra-utils/videos/video-comments' 42} from '../../../../shared/extra-utils/videos/video-comments'
43import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 43import { VideoComment, VideoCommentThreadTree, VideoPrivacy } from '../../../../shared/models/videos'
44import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
45 44
46const expect = chai.expect 45const expect = chai.expect
47 46
diff --git a/server/tests/api/videos/video-comments.ts b/server/tests/api/videos/video-comments.ts
index 615e0ea45..a5ff3a39d 100644
--- a/server/tests/api/videos/video-comments.ts
+++ b/server/tests/api/videos/video-comments.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5 5import { VideoComment, VideoCommentAdmin, VideoCommentThreadTree } from '@shared/models'
6import { cleanupTests, testImage } from '../../../../shared/extra-utils' 6import { cleanupTests, testImage } from '../../../../shared/extra-utils'
7import { 7import {
8 createUser, 8 createUser,
@@ -22,7 +22,6 @@ import {
22 getVideoCommentThreads, 22 getVideoCommentThreads,
23 getVideoThreadComments 23 getVideoThreadComments
24} from '../../../../shared/extra-utils/videos/video-comments' 24} from '../../../../shared/extra-utils/videos/video-comments'
25import { VideoComment, VideoCommentAdmin, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
26 25
27const expect = chai.expect 26const expect = chai.expect
28 27
diff --git a/server/tests/client.ts b/server/tests/client.ts
index 3c99bcd1f..a385edd26 100644
--- a/server/tests/client.ts
+++ b/server/tests/client.ts
@@ -3,7 +3,7 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import * as request from 'supertest' 5import * as request from 'supertest'
6import { Account, VideoPlaylistPrivacy } from '@shared/models' 6import { Account, HTMLServerConfig, ServerConfig, VideoPlaylistPrivacy } from '@shared/models'
7import { 7import {
8 addVideoInPlaylist, 8 addVideoInPlaylist,
9 cleanupTests, 9 cleanupTests,
@@ -11,6 +11,7 @@ import {
11 doubleFollow, 11 doubleFollow,
12 flushAndRunMultipleServers, 12 flushAndRunMultipleServers,
13 getAccount, 13 getAccount,
14 getConfig,
14 getCustomConfig, 15 getCustomConfig,
15 getVideosList, 16 getVideosList,
16 makeHTMLRequest, 17 makeHTMLRequest,
@@ -25,13 +26,17 @@ import {
25 waitJobs 26 waitJobs
26} from '../../shared/extra-utils' 27} from '../../shared/extra-utils'
27import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 28import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
29import { omit } from 'lodash'
28 30
29const expect = chai.expect 31const expect = chai.expect
30 32
31function checkIndexTags (html: string, title: string, description: string, css: string) { 33function checkIndexTags (html: string, title: string, description: string, css: string, config: ServerConfig) {
32 expect(html).to.contain('<title>' + title + '</title>') 34 expect(html).to.contain('<title>' + title + '</title>')
33 expect(html).to.contain('<meta name="description" content="' + description + '" />') 35 expect(html).to.contain('<meta name="description" content="' + description + '" />')
34 expect(html).to.contain('<style class="custom-css-style">' + css + '</style>') 36 expect(html).to.contain('<style class="custom-css-style">' + css + '</style>')
37
38 const htmlConfig: HTMLServerConfig = omit(config, 'signup')
39 expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = '${JSON.stringify(htmlConfig)}'</script>`)
35} 40}
36 41
37describe('Test a client controllers', function () { 42describe('Test a client controllers', function () {
@@ -296,10 +301,11 @@ describe('Test a client controllers', function () {
296 describe('Index HTML', function () { 301 describe('Index HTML', function () {
297 302
298 it('Should have valid index html tags (title, description...)', async function () { 303 it('Should have valid index html tags (title, description...)', async function () {
304 const resConfig = await getConfig(servers[0].url)
299 const res = await makeHTMLRequest(servers[0].url, '/videos/trending') 305 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
300 306
301 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.' 307 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
302 checkIndexTags(res.text, 'PeerTube', description, '') 308 checkIndexTags(res.text, 'PeerTube', description, '', resConfig.body)
303 }) 309 })
304 310
305 it('Should update the customized configuration and have the correct index html tags', async function () { 311 it('Should update the customized configuration and have the correct index html tags', async function () {
@@ -318,15 +324,17 @@ describe('Test a client controllers', function () {
318 } 324 }
319 }) 325 })
320 326
327 const resConfig = await getConfig(servers[0].url)
321 const res = await makeHTMLRequest(servers[0].url, '/videos/trending') 328 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
322 329
323 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }') 330 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body)
324 }) 331 })
325 332
326 it('Should have valid index html updated tags (title, description...)', async function () { 333 it('Should have valid index html updated tags (title, description...)', async function () {
334 const resConfig = await getConfig(servers[0].url)
327 const res = await makeHTMLRequest(servers[0].url, '/videos/trending') 335 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
328 336
329 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }') 337 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body)
330 }) 338 })
331 339
332 it('Should use the original video URL for the canonical tag', async function () { 340 it('Should use the original video URL for the canonical tag', async function () {
@@ -350,6 +358,16 @@ describe('Test a client controllers', function () {
350 }) 358 })
351 }) 359 })
352 360
361 describe('Embed HTML', function () {
362
363 it('Should have the correct embed html tags', async function () {
364 const resConfig = await getConfig(servers[0].url)
365 const res = await makeHTMLRequest(servers[0].url, servers[0].video.embedPath)
366
367 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body)
368 })
369 })
370
353 after(async function () { 371 after(async function () {
354 await cleanupTests(servers) 372 await cleanupTests(servers)
355 }) 373 })
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts
index 7d4f7abb4..1d6bb6cf4 100644
--- a/server/tests/plugins/filter-hooks.ts
+++ b/server/tests/plugins/filter-hooks.ts
@@ -38,6 +38,7 @@ import {
38import { cleanupTests, flushAndRunMultipleServers, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' 38import { cleanupTests, flushAndRunMultipleServers, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers'
39import { getGoodVideoUrl, getMyVideoImports, importVideo } from '../../../shared/extra-utils/videos/video-imports' 39import { getGoodVideoUrl, getMyVideoImports, importVideo } from '../../../shared/extra-utils/videos/video-imports'
40import { 40import {
41 VideoCommentThreadTree,
41 VideoDetails, 42 VideoDetails,
42 VideoImport, 43 VideoImport,
43 VideoImportState, 44 VideoImportState,
@@ -45,7 +46,6 @@ import {
45 VideoPlaylistPrivacy, 46 VideoPlaylistPrivacy,
46 VideoPrivacy 47 VideoPrivacy
47} from '../../../shared/models/videos' 48} from '../../../shared/models/videos'
48import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
49 49
50const expect = chai.expect 50const expect = chai.expect
51 51
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts
index 915995031..b3f57a8f9 100644
--- a/server/tools/peertube-import-videos.ts
+++ b/server/tools/peertube-import-videos.ts
@@ -11,9 +11,9 @@ import { promisify } from 'util'
11import { advancedVideosSearch, getClient, getVideoCategories, login, uploadVideo } from '../../shared/extra-utils/index' 11import { advancedVideosSearch, getClient, getVideoCategories, login, uploadVideo } from '../../shared/extra-utils/index'
12import { sha256 } from '../helpers/core-utils' 12import { sha256 } from '../helpers/core-utils'
13import { doRequestAndSaveToFile } from '../helpers/requests' 13import { doRequestAndSaveToFile } from '../helpers/requests'
14import { buildOriginallyPublishedAt, getYoutubeDLVideoFormat, safeGetYoutubeDL } from '../helpers/youtube-dl'
15import { CONSTRAINTS_FIELDS } from '../initializers/constants' 14import { CONSTRAINTS_FIELDS } from '../initializers/constants'
16import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getLogger, getServerCredentials } from './cli' 15import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getLogger, getServerCredentials } from './cli'
16import { YoutubeDL } from '@server/helpers/youtube-dl'
17 17
18type UserInfo = { 18type UserInfo = {
19 username: string 19 username: string
@@ -74,9 +74,9 @@ async function run (url: string, user: UserInfo) {
74 user.password = await promptPassword() 74 user.password = await promptPassword()
75 } 75 }
76 76
77 const youtubeDL = await safeGetYoutubeDL() 77 const youtubeDLBinary = await YoutubeDL.safeGetYoutubeDL()
78 78
79 let info = await getYoutubeDLInfo(youtubeDL, options.targetUrl, command.args) 79 let info = await getYoutubeDLInfo(youtubeDLBinary, options.targetUrl, command.args)
80 80
81 if (!Array.isArray(info)) info = [ info ] 81 if (!Array.isArray(info)) info = [ info ]
82 82
@@ -86,7 +86,7 @@ async function run (url: string, user: UserInfo) {
86 if (uploadsObject) { 86 if (uploadsObject) {
87 console.log('Fixing URL to %s.', uploadsObject.url) 87 console.log('Fixing URL to %s.', uploadsObject.url)
88 88
89 info = await getYoutubeDLInfo(youtubeDL, uploadsObject.url, command.args) 89 info = await getYoutubeDLInfo(youtubeDLBinary, uploadsObject.url, command.args)
90 } 90 }
91 91
92 let infoArray: any[] 92 let infoArray: any[]
@@ -130,13 +130,14 @@ async function processVideo (parameters: {
130 youtubeInfo: any 130 youtubeInfo: any
131}) { 131}) {
132 const { youtubeInfo, cwd, url, user } = parameters 132 const { youtubeInfo, cwd, url, user } = parameters
133 const youtubeDL = new YoutubeDL('', [])
133 134
134 log.debug('Fetching object.', youtubeInfo) 135 log.debug('Fetching object.', youtubeInfo)
135 136
136 const videoInfo = await fetchObject(youtubeInfo) 137 const videoInfo = await fetchObject(youtubeInfo)
137 log.debug('Fetched object.', videoInfo) 138 log.debug('Fetched object.', videoInfo)
138 139
139 const originallyPublishedAt = buildOriginallyPublishedAt(videoInfo) 140 const originallyPublishedAt = youtubeDL.buildOriginallyPublishedAt(videoInfo)
140 if (options.since && originallyPublishedAt && originallyPublishedAt.getTime() < options.since.getTime()) { 141 if (options.since && originallyPublishedAt && originallyPublishedAt.getTime() < options.since.getTime()) {
141 log.info('Video "%s" has been published before "%s", don\'t upload it.\n', 142 log.info('Video "%s" has been published before "%s", don\'t upload it.\n',
142 videoInfo.title, formatDate(options.since)) 143 videoInfo.title, formatDate(options.since))
@@ -161,13 +162,14 @@ async function processVideo (parameters: {
161 162
162 log.info('Downloading video "%s"...', videoInfo.title) 163 log.info('Downloading video "%s"...', videoInfo.title)
163 164
164 const youtubeDLOptions = [ '-f', getYoutubeDLVideoFormat(), ...command.args, '-o', path ] 165 const youtubeDLOptions = [ '-f', youtubeDL.getYoutubeDLVideoFormat(), ...command.args, '-o', path ]
165 try { 166 try {
166 const youtubeDL = await safeGetYoutubeDL() 167 const youtubeDLBinary = await YoutubeDL.safeGetYoutubeDL()
167 const youtubeDLExec = promisify(youtubeDL.exec).bind(youtubeDL) 168 const youtubeDLExec = promisify(youtubeDLBinary.exec).bind(youtubeDLBinary)
168 const output = await youtubeDLExec(videoInfo.url, youtubeDLOptions, processOptions) 169 const output = await youtubeDLExec(videoInfo.url, youtubeDLOptions, processOptions)
169 log.info(output.join('\n')) 170 log.info(output.join('\n'))
170 await uploadVideoOnPeerTube({ 171 await uploadVideoOnPeerTube({
172 youtubeDL,
171 cwd, 173 cwd,
172 url, 174 url,
173 user, 175 user,
@@ -180,13 +182,14 @@ async function processVideo (parameters: {
180} 182}
181 183
182async function uploadVideoOnPeerTube (parameters: { 184async function uploadVideoOnPeerTube (parameters: {
185 youtubeDL: YoutubeDL
183 videoInfo: any 186 videoInfo: any
184 videoPath: string 187 videoPath: string
185 cwd: string 188 cwd: string
186 url: string 189 url: string
187 user: { username: string, password: string } 190 user: { username: string, password: string }
188}) { 191}) {
189 const { videoInfo, videoPath, cwd, url, user } = parameters 192 const { youtubeDL, videoInfo, videoPath, cwd, url, user } = parameters
190 193
191 const category = await getCategory(videoInfo.categories, url) 194 const category = await getCategory(videoInfo.categories, url)
192 const licence = getLicence(videoInfo.license) 195 const licence = getLicence(videoInfo.license)
@@ -205,7 +208,7 @@ async function uploadVideoOnPeerTube (parameters: {
205 await doRequestAndSaveToFile(videoInfo.thumbnail, thumbnailfile) 208 await doRequestAndSaveToFile(videoInfo.thumbnail, thumbnailfile)
206 } 209 }
207 210
208 const originallyPublishedAt = buildOriginallyPublishedAt(videoInfo) 211 const originallyPublishedAt = youtubeDL.buildOriginallyPublishedAt(videoInfo)
209 212
210 const defaultAttributes = { 213 const defaultAttributes = {
211 name: truncate(videoInfo.title, { 214 name: truncate(videoInfo.title, {
@@ -304,7 +307,7 @@ function fetchObject (info: any) {
304 const url = buildUrl(info) 307 const url = buildUrl(info)
305 308
306 return new Promise<any>(async (res, rej) => { 309 return new Promise<any>(async (res, rej) => {
307 const youtubeDL = await safeGetYoutubeDL() 310 const youtubeDL = await YoutubeDL.safeGetYoutubeDL()
308 youtubeDL.getInfo(url, undefined, processOptions, (err, videoInfo) => { 311 youtubeDL.getInfo(url, undefined, processOptions, (err, videoInfo) => {
309 if (err) return rej(err) 312 if (err) return rej(err)
310 313
diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts
index c8a576844..cb591377b 100644
--- a/server/tools/peertube-plugins.ts
+++ b/server/tools/peertube-plugins.ts
@@ -4,10 +4,9 @@ import { registerTSPaths } from '../helpers/register-ts-paths'
4registerTSPaths() 4registerTSPaths()
5 5
6import * as program from 'commander' 6import * as program from 'commander'
7import { PluginType } from '../../shared/models/plugins/plugin.type'
8import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins' 7import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins'
9import { getAdminTokenOrDie, getServerCredentials } from './cli' 8import { getAdminTokenOrDie, getServerCredentials } from './cli'
10import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model' 9import { PeerTubePlugin, PluginType } from '../../shared/models'
11import { isAbsolute } from 'path' 10import { isAbsolute } from 'path'
12import * as CliTable3 from 'cli-table3' 11import * as CliTable3 from 'cli-table3'
13import commander = require('commander') 12import commander = require('commander')
diff --git a/server/types/models/moderation/abuse-message.ts b/server/types/models/abuse/abuse-message.ts
index 565eca706..565eca706 100644
--- a/server/types/models/moderation/abuse-message.ts
+++ b/server/types/models/abuse/abuse-message.ts
diff --git a/server/types/models/moderation/abuse.ts b/server/types/models/abuse/abuse.ts
index 6fd83684c..6fd83684c 100644
--- a/server/types/models/moderation/abuse.ts
+++ b/server/types/models/abuse/abuse.ts
diff --git a/server/types/models/moderation/index.ts b/server/types/models/abuse/index.ts
index 1ed91b249..1ed91b249 100644
--- a/server/types/models/moderation/index.ts
+++ b/server/types/models/abuse/index.ts
diff --git a/server/types/models/account/account.ts b/server/types/models/account/account.ts
index 9513acad8..984841291 100644
--- a/server/types/models/account/account.ts
+++ b/server/types/models/account/account.ts
@@ -1,7 +1,5 @@
1import { FunctionProperties, PickWith } from '@shared/core-utils' 1import { FunctionProperties, PickWith } from '@shared/core-utils'
2import { AccountModel } from '../../../models/account/account' 2import { AccountModel } from '../../../models/account/account'
3import { MChannelDefault } from '../video/video-channels'
4import { MAccountBlocklistId } from './account-blocklist'
5import { 3import {
6 MActor, 4 MActor,
7 MActorAPAccount, 5 MActorAPAccount,
@@ -15,7 +13,9 @@ import {
15 MActorSummary, 13 MActorSummary,
16 MActorSummaryFormattable, 14 MActorSummaryFormattable,
17 MActorUrl 15 MActorUrl
18} from './actor' 16} from '../actor'
17import { MChannelDefault } from '../video/video-channels'
18import { MAccountBlocklistId } from './account-blocklist'
19 19
20type Use<K extends keyof AccountModel, M> = PickWith<AccountModel, K, M> 20type Use<K extends keyof AccountModel, M> = PickWith<AccountModel, K, M>
21 21
diff --git a/server/types/models/account/index.ts b/server/types/models/account/index.ts
index e3fc00f94..dab2eea7e 100644
--- a/server/types/models/account/index.ts
+++ b/server/types/models/account/index.ts
@@ -1,5 +1,2 @@
1export * from './account' 1export * from './account'
2export * from './account-blocklist' 2export * from './account-blocklist'
3export * from './actor-follow'
4export * from './actor-image'
5export * from './actor'
diff --git a/server/types/models/account/actor-follow.ts b/server/types/models/actor/actor-follow.ts
index 8e19c6140..98a6ca8a5 100644
--- a/server/types/models/account/actor-follow.ts
+++ b/server/types/models/actor/actor-follow.ts
@@ -1,5 +1,5 @@
1import { PickWith } from '@shared/core-utils' 1import { PickWith } from '@shared/core-utils'
2import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 2import { ActorFollowModel } from '../../../models/actor/actor-follow'
3import { 3import {
4 MActor, 4 MActor,
5 MActorChannelAccountActor, 5 MActorChannelAccountActor,
diff --git a/server/types/models/account/actor-image.ts b/server/types/models/actor/actor-image.ts
index e59f8b141..89adb01ae 100644
--- a/server/types/models/account/actor-image.ts
+++ b/server/types/models/actor/actor-image.ts
@@ -1,5 +1,5 @@
1import { ActorImageModel } from '../../../models/account/actor-image'
2import { FunctionProperties } from '@shared/core-utils' 1import { FunctionProperties } from '@shared/core-utils'
2import { ActorImageModel } from '../../../models/actor/actor-image'
3 3
4export type MActorImage = ActorImageModel 4export type MActorImage = ActorImageModel
5 5
diff --git a/server/types/models/account/actor.ts b/server/types/models/actor/actor.ts
index 0b620872e..b3a70cbce 100644
--- a/server/types/models/account/actor.ts
+++ b/server/types/models/actor/actor.ts
@@ -1,9 +1,8 @@
1
2import { FunctionProperties, PickWith, PickWithOpt } from '@shared/core-utils' 1import { FunctionProperties, PickWith, PickWithOpt } from '@shared/core-utils'
3import { ActorModel } from '../../../models/activitypub/actor' 2import { ActorModel } from '../../../models/actor/actor'
3import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from '../account'
4import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server' 4import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server'
5import { MChannel, MChannelAccountActor, MChannelAccountDefault, MChannelId, MChannelIdActor } from '../video' 5import { MChannel, MChannelAccountActor, MChannelAccountDefault, MChannelId, MChannelIdActor } from '../video'
6import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from './account'
7import { MActorImage, MActorImageFormattable } from './actor-image' 6import { MActorImage, MActorImageFormattable } from './actor-image'
8 7
9type Use<K extends keyof ActorModel, M> = PickWith<ActorModel, K, M> 8type Use<K extends keyof ActorModel, M> = PickWith<ActorModel, K, M>
diff --git a/server/types/models/actor/index.ts b/server/types/models/actor/index.ts
new file mode 100644
index 000000000..b27815255
--- /dev/null
+++ b/server/types/models/actor/index.ts
@@ -0,0 +1,3 @@
1export * from './actor-follow'
2export * from './actor-image'
3export * from './actor'
diff --git a/server/types/models/index.ts b/server/types/models/index.ts
index b4fdb1ff3..704cb9844 100644
--- a/server/types/models/index.ts
+++ b/server/types/models/index.ts
@@ -1,6 +1,7 @@
1export * from './abuse'
1export * from './account' 2export * from './account'
3export * from './actor'
2export * from './application' 4export * from './application'
3export * from './moderation'
4export * from './oauth' 5export * from './oauth'
5export * from './server' 6export * from './server'
6export * from './user' 7export * from './user'
diff --git a/server/types/models/user/user-notification-setting.ts b/server/types/models/user/user-notification-setting.ts
index c674add1b..d1db645e7 100644
--- a/server/types/models/user/user-notification-setting.ts
+++ b/server/types/models/user/user-notification-setting.ts
@@ -1,4 +1,4 @@
1import { UserNotificationSettingModel } from '@server/models/account/user-notification-setting' 1import { UserNotificationSettingModel } from '@server/models/user/user-notification-setting'
2 2
3export type MNotificationSetting = Omit<UserNotificationSettingModel, 'User'> 3export type MNotificationSetting = Omit<UserNotificationSettingModel, 'User'>
4 4
diff --git a/server/types/models/user/user-notification.ts b/server/types/models/user/user-notification.ts
index 7ebb0485d..918614dd1 100644
--- a/server/types/models/user/user-notification.ts
+++ b/server/types/models/user/user-notification.ts
@@ -2,13 +2,13 @@ import { VideoAbuseModel } from '@server/models/abuse/video-abuse'
2import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' 2import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse'
3import { ApplicationModel } from '@server/models/application/application' 3import { ApplicationModel } from '@server/models/application/application'
4import { PluginModel } from '@server/models/server/plugin' 4import { PluginModel } from '@server/models/server/plugin'
5import { UserNotificationModel } from '@server/models/user/user-notification'
5import { PickWith, PickWithOpt } from '@shared/core-utils' 6import { PickWith, PickWithOpt } from '@shared/core-utils'
6import { AbuseModel } from '../../../models/abuse/abuse' 7import { AbuseModel } from '../../../models/abuse/abuse'
7import { AccountModel } from '../../../models/account/account' 8import { AccountModel } from '../../../models/account/account'
8import { ActorImageModel } from '../../../models/account/actor-image' 9import { ActorModel } from '../../../models/actor/actor'
9import { UserNotificationModel } from '../../../models/account/user-notification' 10import { ActorFollowModel } from '../../../models/actor/actor-follow'
10import { ActorModel } from '../../../models/activitypub/actor' 11import { ActorImageModel } from '../../../models/actor/actor-image'
11import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
12import { ServerModel } from '../../../models/server/server' 12import { ServerModel } from '../../../models/server/server'
13import { VideoModel } from '../../../models/video/video' 13import { VideoModel } from '../../../models/video/video'
14import { VideoBlacklistModel } from '../../../models/video/video-blacklist' 14import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
diff --git a/server/types/models/user/user-video-history.ts b/server/types/models/user/user-video-history.ts
index 62673ab1b..34e2930e7 100644
--- a/server/types/models/user/user-video-history.ts
+++ b/server/types/models/user/user-video-history.ts
@@ -1,4 +1,4 @@
1import { UserVideoHistoryModel } from '../../../models/account/user-video-history' 1import { UserVideoHistoryModel } from '../../../models/user/user-video-history'
2 2
3export type MUserVideoHistory = Omit<UserVideoHistoryModel, 'Video' | 'User'> 3export type MUserVideoHistory = Omit<UserVideoHistoryModel, 'Video' | 'User'>
4 4
diff --git a/server/types/models/user/user.ts b/server/types/models/user/user.ts
index fa7de9c52..f79220e11 100644
--- a/server/types/models/user/user.ts
+++ b/server/types/models/user/user.ts
@@ -1,7 +1,7 @@
1import { AccountModel } from '@server/models/account/account' 1import { AccountModel } from '@server/models/account/account'
2import { UserModel } from '@server/models/user/user'
2import { MVideoPlaylist } from '@server/types/models' 3import { MVideoPlaylist } from '@server/types/models'
3import { PickWith, PickWithOpt } from '@shared/core-utils' 4import { PickWith, PickWithOpt } from '@shared/core-utils'
4import { UserModel } from '../../../models/account/user'
5import { 5import {
6 MAccount, 6 MAccount,
7 MAccountDefault, 7 MAccountDefault,
diff --git a/server/types/models/video/video-channels.ts b/server/types/models/video/video-channels.ts
index f577807ca..c147567d9 100644
--- a/server/types/models/video/video-channels.ts
+++ b/server/types/models/video/video-channels.ts
@@ -9,7 +9,9 @@ import {
9 MAccountSummaryBlocks, 9 MAccountSummaryBlocks,
10 MAccountSummaryFormattable, 10 MAccountSummaryFormattable,
11 MAccountUrl, 11 MAccountUrl,
12 MAccountUserId, 12 MAccountUserId
13} from '../account'
14import {
13 MActor, 15 MActor,
14 MActorAccountChannelId, 16 MActorAccountChannelId,
15 MActorAPChannel, 17 MActorAPChannel,
@@ -23,7 +25,7 @@ import {
23 MActorSummary, 25 MActorSummary,
24 MActorSummaryFormattable, 26 MActorSummaryFormattable,
25 MActorUrl 27 MActorUrl
26} from '../account' 28} from '../actor'
27import { MVideo } from './video' 29import { MVideo } from './video'
28 30
29type Use<K extends keyof VideoChannelModel, M> = PickWith<VideoChannelModel, K, M> 31type Use<K extends keyof VideoChannelModel, M> = PickWith<VideoChannelModel, K, M>
diff --git a/server/types/models/video/video-share.ts b/server/types/models/video/video-share.ts
index b7a783bb6..78f44e58c 100644
--- a/server/types/models/video/video-share.ts
+++ b/server/types/models/video/video-share.ts
@@ -1,6 +1,6 @@
1import { VideoShareModel } from '../../../models/video/video-share'
2import { PickWith } from '@shared/core-utils' 1import { PickWith } from '@shared/core-utils'
3import { MActorDefault } from '../account' 2import { VideoShareModel } from '../../../models/video/video-share'
3import { MActorDefault } from '../actor'
4import { MVideo } from './video' 4import { MVideo } from './video'
5 5
6type Use<K extends keyof VideoShareModel, M> = PickWith<VideoShareModel, K, M> 6type Use<K extends keyof VideoShareModel, M> = PickWith<VideoShareModel, K, M>
diff --git a/server/types/plugins/register-server-option.model.ts b/server/types/plugins/register-server-option.model.ts
index 2432b7ac4..8774bcd8c 100644
--- a/server/types/plugins/register-server-option.model.ts
+++ b/server/types/plugins/register-server-option.model.ts
@@ -1,6 +1,6 @@
1import { Router, Response } from 'express' 1import { Response, Router } from 'express'
2import { Logger } from 'winston' 2import { Logger } from 'winston'
3import { ActorModel } from '@server/models/activitypub/actor' 3import { ActorModel } from '@server/models/actor/actor'
4import { 4import {
5 PluginPlaylistPrivacyManager, 5 PluginPlaylistPrivacyManager,
6 PluginSettingsManager, 6 PluginSettingsManager,
diff --git a/server/types/sequelize.ts b/server/types/sequelize.ts
index 9cd83612d..535113d01 100644
--- a/server/types/sequelize.ts
+++ b/server/types/sequelize.ts
@@ -1,4 +1,5 @@
1import { Model } from 'sequelize-typescript' 1import { AttributesOnly } from '@shared/core-utils'
2import { Model } from 'sequelize'
2 3
3// Thanks to sequelize-typescript: https://github.com/RobinBuschmann/sequelize-typescript 4// Thanks to sequelize-typescript: https://github.com/RobinBuschmann/sequelize-typescript
4 5
@@ -9,7 +10,7 @@ export type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] }
9 10
10export type RecursivePartial<T> = { [P in keyof T]?: RecursivePartial<T[P]> } 11export type RecursivePartial<T> = { [P in keyof T]?: RecursivePartial<T[P]> }
11 12
12export type FilteredModelAttributes<T extends Model<T>> = RecursivePartial<Omit<T, keyof Model<any>>> & { 13export type FilteredModelAttributes<T extends Model<any>> = Partial<AttributesOnly<T>> & {
13 id?: number | any 14 id?: number | any
14 createdAt?: Date | any 15 createdAt?: Date | any
15 updatedAt?: Date | any 16 updatedAt?: Date | any
diff --git a/shared/core-utils/miscs/types.ts b/shared/core-utils/miscs/types.ts
index bb64dc830..bd2a97b98 100644
--- a/shared/core-utils/miscs/types.ts
+++ b/shared/core-utils/miscs/types.ts
@@ -6,6 +6,10 @@ export type FunctionPropertyNames<T> = {
6 6
7export type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>> 7export type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>
8 8
9export type AttributesOnly<T> = {
10 [K in keyof T]: T[K] extends Function ? never : T[K]
11}
12
9export type PickWith<T, KT extends keyof T, V> = { 13export type PickWith<T, KT extends keyof T, V> = {
10 [P in KT]: T[P] extends V ? V : never 14 [P in KT]: T[P] extends V ? V : never
11} 15}
diff --git a/shared/extra-utils/index.ts b/shared/extra-utils/index.ts
index 898a92d43..720db19cb 100644
--- a/shared/extra-utils/index.ts
+++ b/shared/extra-utils/index.ts
@@ -1,15 +1,24 @@
1export * from './bulk/bulk' 1export * from './bulk/bulk'
2
2export * from './cli/cli' 3export * from './cli/cli'
4
3export * from './feeds/feeds' 5export * from './feeds/feeds'
6
4export * from './mock-servers/mock-instances-index' 7export * from './mock-servers/mock-instances-index'
5export * from './miscs/miscs' 8
9export * from './miscs/email'
6export * from './miscs/sql' 10export * from './miscs/sql'
11export * from './miscs/miscs'
7export * from './miscs/stubs' 12export * from './miscs/stubs'
13
8export * from './moderation/abuses' 14export * from './moderation/abuses'
9export * from './plugins/mock-blocklist' 15export * from './plugins/mock-blocklist'
16
10export * from './requests/check-api-params' 17export * from './requests/check-api-params'
11export * from './requests/requests' 18export * from './requests/requests'
19
12export * from './search/videos' 20export * from './search/videos'
21
13export * from './server/activitypub' 22export * from './server/activitypub'
14export * from './server/clients' 23export * from './server/clients'
15export * from './server/config' 24export * from './server/config'
@@ -18,9 +27,14 @@ export * from './server/follows'
18export * from './server/jobs' 27export * from './server/jobs'
19export * from './server/plugins' 28export * from './server/plugins'
20export * from './server/servers' 29export * from './server/servers'
30
21export * from './users/accounts' 31export * from './users/accounts'
32export * from './users/blocklist'
22export * from './users/login' 33export * from './users/login'
34export * from './users/user-notifications'
35export * from './users/user-subscriptions'
23export * from './users/users' 36export * from './users/users'
37
24export * from './videos/live' 38export * from './videos/live'
25export * from './videos/services' 39export * from './videos/services'
26export * from './videos/video-blacklist' 40export * from './videos/video-blacklist'
diff --git a/shared/extra-utils/server/plugins.ts b/shared/extra-utils/server/plugins.ts
index 864954ee7..d53e5b382 100644
--- a/shared/extra-utils/server/plugins.ts
+++ b/shared/extra-utils/server/plugins.ts
@@ -4,12 +4,12 @@ import { expect } from 'chai'
4import { readJSON, writeJSON } from 'fs-extra' 4import { readJSON, writeJSON } from 'fs-extra'
5import { join } from 'path' 5import { join } from 'path'
6import { RegisteredServerSettings } from '@shared/models' 6import { RegisteredServerSettings } from '@shared/models'
7import { PeertubePluginIndexList } from '../../models/plugins/peertube-plugin-index-list.model' 7import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
8import { PeertubePluginIndexList } from '../../models/plugins/plugin-index/peertube-plugin-index-list.model'
8import { PluginType } from '../../models/plugins/plugin.type' 9import { PluginType } from '../../models/plugins/plugin.type'
9import { buildServerDirectory, root } from '../miscs/miscs' 10import { buildServerDirectory, root } from '../miscs/miscs'
10import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' 11import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
11import { ServerInfo } from './servers' 12import { ServerInfo } from './servers'
12import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
13 13
14function listPlugins (parameters: { 14function listPlugins (parameters: {
15 url: string 15 url: string
diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts
index 479f08e12..d04757470 100644
--- a/shared/extra-utils/server/servers.ts
+++ b/shared/extra-utils/server/servers.ts
@@ -45,9 +45,12 @@ interface ServerInfo {
45 uuid: string 45 uuid: string
46 name?: string 46 name?: string
47 url?: string 47 url?: string
48
48 account?: { 49 account?: {
49 name: string 50 name: string
50 } 51 }
52
53 embedPath?: string
51 } 54 }
52 55
53 remoteVideo?: { 56 remoteVideo?: {
diff --git a/shared/models/nodeinfo/index.ts b/shared/models/nodeinfo/index.ts
new file mode 100644
index 000000000..faa64302a
--- /dev/null
+++ b/shared/models/nodeinfo/index.ts
@@ -0,0 +1 @@
export * from './nodeinfo.model'
diff --git a/shared/models/nodeinfo/index.d.ts b/shared/models/nodeinfo/nodeinfo.model.ts
index 336cb66d2..336cb66d2 100644
--- a/shared/models/nodeinfo/index.d.ts
+++ b/shared/models/nodeinfo/nodeinfo.model.ts
diff --git a/shared/models/overviews/index.ts b/shared/models/overviews/index.ts
index 376609efa..468507c6b 100644
--- a/shared/models/overviews/index.ts
+++ b/shared/models/overviews/index.ts
@@ -1 +1 @@
export * from './videos-overview' export * from './videos-overview.model'
diff --git a/shared/models/overviews/videos-overview.ts b/shared/models/overviews/videos-overview.model.ts
index 0f3cb4a52..0f3cb4a52 100644
--- a/shared/models/overviews/videos-overview.ts
+++ b/shared/models/overviews/videos-overview.model.ts
diff --git a/shared/models/plugins/client-hook.model.ts b/shared/models/plugins/client/client-hook.model.ts
index 620651051..620651051 100644
--- a/shared/models/plugins/client-hook.model.ts
+++ b/shared/models/plugins/client/client-hook.model.ts
diff --git a/shared/models/plugins/client/index.ts b/shared/models/plugins/client/index.ts
new file mode 100644
index 000000000..6dfc6351f
--- /dev/null
+++ b/shared/models/plugins/client/index.ts
@@ -0,0 +1,6 @@
1export * from './client-hook.model'
2export * from './plugin-client-scope.type'
3export * from './plugin-element-placeholder.type'
4export * from './register-client-form-field.model'
5export * from './register-client-hook.model'
6export * from './register-client-settings-script.model'
diff --git a/shared/models/plugins/plugin-client-scope.type.ts b/shared/models/plugins/client/plugin-client-scope.type.ts
index 8cc234ff2..8cc234ff2 100644
--- a/shared/models/plugins/plugin-client-scope.type.ts
+++ b/shared/models/plugins/client/plugin-client-scope.type.ts
diff --git a/shared/models/plugins/plugin-element-placeholder.type.ts b/shared/models/plugins/client/plugin-element-placeholder.type.ts
index 129099c62..129099c62 100644
--- a/shared/models/plugins/plugin-element-placeholder.type.ts
+++ b/shared/models/plugins/client/plugin-element-placeholder.type.ts
diff --git a/shared/models/plugins/register-client-form-field.model.ts b/shared/models/plugins/client/register-client-form-field.model.ts
index 2df071337..2df071337 100644
--- a/shared/models/plugins/register-client-form-field.model.ts
+++ b/shared/models/plugins/client/register-client-form-field.model.ts
diff --git a/shared/models/plugins/register-client-hook.model.ts b/shared/models/plugins/client/register-client-hook.model.ts
index 81047b21d..81047b21d 100644
--- a/shared/models/plugins/register-client-hook.model.ts
+++ b/shared/models/plugins/client/register-client-hook.model.ts
diff --git a/shared/models/plugins/register-client-settings-script.model.ts b/shared/models/plugins/client/register-client-settings-script.model.ts
index ac16af366..481ceef96 100644
--- a/shared/models/plugins/register-client-settings-script.model.ts
+++ b/shared/models/plugins/client/register-client-settings-script.model.ts
@@ -1,4 +1,4 @@
1import { RegisterServerSettingOptions } from "./register-server-setting.model" 1import { RegisterServerSettingOptions } from '../server'
2 2
3export interface RegisterClientSettingsScript { 3export interface RegisterClientSettingsScript {
4 isSettingHidden (options: { 4 isSettingHidden (options: {
diff --git a/shared/models/plugins/index.ts b/shared/models/plugins/index.ts
index 03b27f907..cbbe4916e 100644
--- a/shared/models/plugins/index.ts
+++ b/shared/models/plugins/index.ts
@@ -1,28 +1,6 @@
1export * from './client-hook.model' 1export * from './client'
2export * from './plugin-index'
3export * from './server'
2export * from './hook-type.enum' 4export * from './hook-type.enum'
3export * from './install-plugin.model'
4export * from './manage-plugin.model'
5export * from './peertube-plugin-index-list.model'
6export * from './peertube-plugin-index.model'
7export * from './peertube-plugin-latest-version.model'
8export * from './peertube-plugin.model'
9export * from './plugin-client-scope.type'
10export * from './plugin-element-placeholder.type'
11export * from './plugin-package-json.model' 5export * from './plugin-package-json.model'
12export * from './plugin-playlist-privacy-manager.model'
13export * from './plugin-settings-manager.model'
14export * from './plugin-storage-manager.model'
15export * from './plugin-transcoding-manager.model'
16export * from './plugin-translation.model'
17export * from './plugin-video-category-manager.model'
18export * from './plugin-video-language-manager.model'
19export * from './plugin-video-licence-manager.model'
20export * from './plugin-video-privacy-manager.model'
21export * from './plugin.type' 6export * from './plugin.type'
22export * from './public-server.setting'
23export * from './register-client-hook.model'
24export * from './register-client-settings-script.model'
25export * from './register-client-form-field.model'
26export * from './register-server-hook.model'
27export * from './register-server-setting.model'
28export * from './server-hook.model'
diff --git a/shared/models/plugins/plugin-index/index.ts b/shared/models/plugins/plugin-index/index.ts
new file mode 100644
index 000000000..913846638
--- /dev/null
+++ b/shared/models/plugins/plugin-index/index.ts
@@ -0,0 +1,3 @@
1export * from './peertube-plugin-index-list.model'
2export * from './peertube-plugin-index.model'
3export * from './peertube-plugin-latest-version.model'
diff --git a/shared/models/plugins/peertube-plugin-index-list.model.ts b/shared/models/plugins/plugin-index/peertube-plugin-index-list.model.ts
index 817bac31e..ecb46482e 100644
--- a/shared/models/plugins/peertube-plugin-index-list.model.ts
+++ b/shared/models/plugins/plugin-index/peertube-plugin-index-list.model.ts
@@ -1,4 +1,4 @@
1import { PluginType } from './plugin.type' 1import { PluginType } from '../plugin.type'
2 2
3export interface PeertubePluginIndexList { 3export interface PeertubePluginIndexList {
4 start: number 4 start: number
diff --git a/shared/models/plugins/peertube-plugin-index.model.ts b/shared/models/plugins/plugin-index/peertube-plugin-index.model.ts
index e91c8b4dc..e91c8b4dc 100644
--- a/shared/models/plugins/peertube-plugin-index.model.ts
+++ b/shared/models/plugins/plugin-index/peertube-plugin-index.model.ts
diff --git a/shared/models/plugins/peertube-plugin-latest-version.model.ts b/shared/models/plugins/plugin-index/peertube-plugin-latest-version.model.ts
index 811a64429..811a64429 100644
--- a/shared/models/plugins/peertube-plugin-latest-version.model.ts
+++ b/shared/models/plugins/plugin-index/peertube-plugin-latest-version.model.ts
diff --git a/shared/models/plugins/plugin-package-json.model.ts b/shared/models/plugins/plugin-package-json.model.ts
index c26e9ae5b..b2f92af80 100644
--- a/shared/models/plugins/plugin-package-json.model.ts
+++ b/shared/models/plugins/plugin-package-json.model.ts
@@ -1,4 +1,4 @@
1import { PluginClientScope } from './plugin-client-scope.type' 1import { PluginClientScope } from './client/plugin-client-scope.type'
2 2
3export type PluginTranslationPaths = { 3export type PluginTranslationPaths = {
4 [ locale: string ]: string 4 [ locale: string ]: string
diff --git a/shared/models/plugins/server/api/index.ts b/shared/models/plugins/server/api/index.ts
new file mode 100644
index 000000000..eb59a03f0
--- /dev/null
+++ b/shared/models/plugins/server/api/index.ts
@@ -0,0 +1,3 @@
1export * from './install-plugin.model'
2export * from './manage-plugin.model'
3export * from './peertube-plugin.model'
diff --git a/shared/models/plugins/install-plugin.model.ts b/shared/models/plugins/server/api/install-plugin.model.ts
index 5a268ebe1..5a268ebe1 100644
--- a/shared/models/plugins/install-plugin.model.ts
+++ b/shared/models/plugins/server/api/install-plugin.model.ts
diff --git a/shared/models/plugins/manage-plugin.model.ts b/shared/models/plugins/server/api/manage-plugin.model.ts
index 612b3056c..612b3056c 100644
--- a/shared/models/plugins/manage-plugin.model.ts
+++ b/shared/models/plugins/server/api/manage-plugin.model.ts
diff --git a/shared/models/plugins/peertube-plugin.model.ts b/shared/models/plugins/server/api/peertube-plugin.model.ts
index 2b0bb8cfa..54c383f57 100644
--- a/shared/models/plugins/peertube-plugin.model.ts
+++ b/shared/models/plugins/server/api/peertube-plugin.model.ts
@@ -1,4 +1,4 @@
1import { PluginType } from './plugin.type' 1import { PluginType } from '../../plugin.type'
2 2
3export interface PeerTubePlugin { 3export interface PeerTubePlugin {
4 name: string 4 name: string
diff --git a/shared/models/plugins/server/index.ts b/shared/models/plugins/server/index.ts
new file mode 100644
index 000000000..d3ff49d3b
--- /dev/null
+++ b/shared/models/plugins/server/index.ts
@@ -0,0 +1,6 @@
1export * from './api'
2export * from './managers'
3export * from './settings'
4export * from './plugin-translation.model'
5export * from './register-server-hook.model'
6export * from './server-hook.model'
diff --git a/shared/models/plugins/server/managers/index.ts b/shared/models/plugins/server/managers/index.ts
new file mode 100644
index 000000000..49365a854
--- /dev/null
+++ b/shared/models/plugins/server/managers/index.ts
@@ -0,0 +1,9 @@
1
2export * from './plugin-playlist-privacy-manager.model'
3export * from './plugin-settings-manager.model'
4export * from './plugin-storage-manager.model'
5export * from './plugin-transcoding-manager.model'
6export * from './plugin-video-category-manager.model'
7export * from './plugin-video-language-manager.model'
8export * from './plugin-video-licence-manager.model'
9export * from './plugin-video-privacy-manager.model'
diff --git a/shared/models/plugins/plugin-playlist-privacy-manager.model.ts b/shared/models/plugins/server/managers/plugin-playlist-privacy-manager.model.ts
index d1823ef4e..4703c0a8b 100644
--- a/shared/models/plugins/plugin-playlist-privacy-manager.model.ts
+++ b/shared/models/plugins/server/managers/plugin-playlist-privacy-manager.model.ts
@@ -1,4 +1,4 @@
1import { VideoPlaylistPrivacy } from '../videos/playlist/video-playlist-privacy.model' 1import { VideoPlaylistPrivacy } from '../../../videos/playlist/video-playlist-privacy.model'
2 2
3export interface PluginPlaylistPrivacyManager { 3export interface PluginPlaylistPrivacyManager {
4 // PUBLIC = 1, 4 // PUBLIC = 1,
diff --git a/shared/models/plugins/plugin-settings-manager.model.ts b/shared/models/plugins/server/managers/plugin-settings-manager.model.ts
index 3c28c0565..3c28c0565 100644
--- a/shared/models/plugins/plugin-settings-manager.model.ts
+++ b/shared/models/plugins/server/managers/plugin-settings-manager.model.ts
diff --git a/shared/models/plugins/plugin-storage-manager.model.ts b/shared/models/plugins/server/managers/plugin-storage-manager.model.ts
index 51567044a..51567044a 100644
--- a/shared/models/plugins/plugin-storage-manager.model.ts
+++ b/shared/models/plugins/server/managers/plugin-storage-manager.model.ts
diff --git a/shared/models/plugins/plugin-transcoding-manager.model.ts b/shared/models/plugins/server/managers/plugin-transcoding-manager.model.ts
index 8babccd4e..a0422a460 100644
--- a/shared/models/plugins/plugin-transcoding-manager.model.ts
+++ b/shared/models/plugins/server/managers/plugin-transcoding-manager.model.ts
@@ -1,4 +1,4 @@
1import { EncoderOptionsBuilder } from '../videos/video-transcoding.model' 1import { EncoderOptionsBuilder } from '../../../videos/video-transcoding.model'
2 2
3export interface PluginTranscodingManager { 3export interface PluginTranscodingManager {
4 addLiveProfile (encoder: string, profile: string, builder: EncoderOptionsBuilder): boolean 4 addLiveProfile (encoder: string, profile: string, builder: EncoderOptionsBuilder): boolean
diff --git a/shared/models/plugins/plugin-video-category-manager.model.ts b/shared/models/plugins/server/managers/plugin-video-category-manager.model.ts
index 201bfa979..201bfa979 100644
--- a/shared/models/plugins/plugin-video-category-manager.model.ts
+++ b/shared/models/plugins/server/managers/plugin-video-category-manager.model.ts
diff --git a/shared/models/plugins/plugin-video-language-manager.model.ts b/shared/models/plugins/server/managers/plugin-video-language-manager.model.ts
index 3fd577a79..3fd577a79 100644
--- a/shared/models/plugins/plugin-video-language-manager.model.ts
+++ b/shared/models/plugins/server/managers/plugin-video-language-manager.model.ts
diff --git a/shared/models/plugins/plugin-video-licence-manager.model.ts b/shared/models/plugins/server/managers/plugin-video-licence-manager.model.ts
index 82a634d3a..82a634d3a 100644
--- a/shared/models/plugins/plugin-video-licence-manager.model.ts
+++ b/shared/models/plugins/server/managers/plugin-video-licence-manager.model.ts
diff --git a/shared/models/plugins/plugin-video-privacy-manager.model.ts b/shared/models/plugins/server/managers/plugin-video-privacy-manager.model.ts
index 3ada99608..7717115e3 100644
--- a/shared/models/plugins/plugin-video-privacy-manager.model.ts
+++ b/shared/models/plugins/server/managers/plugin-video-privacy-manager.model.ts
@@ -1,4 +1,4 @@
1import { VideoPrivacy } from '../videos/video-privacy.enum' 1import { VideoPrivacy } from '../../../videos/video-privacy.enum'
2 2
3export interface PluginVideoPrivacyManager { 3export interface PluginVideoPrivacyManager {
4 // PUBLIC = 1 4 // PUBLIC = 1
diff --git a/shared/models/plugins/plugin-translation.model.ts b/shared/models/plugins/server/plugin-translation.model.ts
index a2dd8e560..a2dd8e560 100644
--- a/shared/models/plugins/plugin-translation.model.ts
+++ b/shared/models/plugins/server/plugin-translation.model.ts
diff --git a/shared/models/plugins/register-server-hook.model.ts b/shared/models/plugins/server/register-server-hook.model.ts
index 746fdc329..746fdc329 100644
--- a/shared/models/plugins/register-server-hook.model.ts
+++ b/shared/models/plugins/server/register-server-hook.model.ts
diff --git a/shared/models/plugins/server-hook.model.ts b/shared/models/plugins/server/server-hook.model.ts
index 88277af5a..88277af5a 100644
--- a/shared/models/plugins/server-hook.model.ts
+++ b/shared/models/plugins/server/server-hook.model.ts
diff --git a/shared/models/plugins/server/settings/index.ts b/shared/models/plugins/server/settings/index.ts
new file mode 100644
index 000000000..b456de019
--- /dev/null
+++ b/shared/models/plugins/server/settings/index.ts
@@ -0,0 +1,2 @@
1export * from './public-server.setting'
2export * from './register-server-setting.model'
diff --git a/shared/models/plugins/public-server.setting.ts b/shared/models/plugins/server/settings/public-server.setting.ts
index 9802c4d7d..9802c4d7d 100644
--- a/shared/models/plugins/public-server.setting.ts
+++ b/shared/models/plugins/server/settings/public-server.setting.ts
diff --git a/shared/models/plugins/register-server-setting.model.ts b/shared/models/plugins/server/settings/register-server-setting.model.ts
index 9f45c3c37..d9a798cac 100644
--- a/shared/models/plugins/register-server-setting.model.ts
+++ b/shared/models/plugins/server/settings/register-server-setting.model.ts
@@ -1,4 +1,4 @@
1import { RegisterClientFormFieldOptions } from './register-client-form-field.model' 1import { RegisterClientFormFieldOptions } from '../../client'
2 2
3export type RegisterServerSettingOptions = RegisterClientFormFieldOptions & { 3export type RegisterServerSettingOptions = RegisterClientFormFieldOptions & {
4 // If the setting is not private, anyone can view its value (client code included) 4 // If the setting is not private, anyone can view its value (client code included)
diff --git a/shared/models/redundancy/index.ts b/shared/models/redundancy/index.ts
index 649cc489f..641a5d625 100644
--- a/shared/models/redundancy/index.ts
+++ b/shared/models/redundancy/index.ts
@@ -1,3 +1,4 @@
1export * from './videos-redundancy-strategy.model'
2export * from './video-redundancies-filters.model' 1export * from './video-redundancies-filters.model'
2export * from './video-redundancy-config-filter.type'
3export * from './video-redundancy.model' 3export * from './video-redundancy.model'
4export * from './videos-redundancy-strategy.model'
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts
index 85d84af44..2c5026b30 100644
--- a/shared/models/server/server-config.model.ts
+++ b/shared/models/server/server-config.model.ts
@@ -215,3 +215,5 @@ export interface ServerConfig {
215 dismissable: boolean 215 dismissable: boolean
216 } 216 }
217} 217}
218
219export type HTMLServerConfig = Omit<ServerConfig, 'signup'>
diff --git a/shared/models/server/server-error-code.enum.ts b/shared/models/server/server-error-code.enum.ts
index c02b0e6c7..d17d958be 100644
--- a/shared/models/server/server-error-code.enum.ts
+++ b/shared/models/server/server-error-code.enum.ts
@@ -2,4 +2,5 @@ export const enum ServerErrorCode {
2 DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS = 1, 2 DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS = 1,
3 MAX_INSTANCE_LIVES_LIMIT_REACHED = 2, 3 MAX_INSTANCE_LIVES_LIMIT_REACHED = 2,
4 MAX_USER_LIVES_LIMIT_REACHED = 3, 4 MAX_USER_LIVES_LIMIT_REACHED = 3,
5 INCORRECT_FILES_IN_TORRENT = 4
5} 6}
diff --git a/shared/models/videos/change-ownership/index.ts b/shared/models/videos/change-ownership/index.ts
new file mode 100644
index 000000000..a942fb2cd
--- /dev/null
+++ b/shared/models/videos/change-ownership/index.ts
@@ -0,0 +1,3 @@
1export * from './video-change-ownership-accept.model'
2export * from './video-change-ownership-create.model'
3export * from './video-change-ownership.model'
diff --git a/shared/models/videos/video-change-ownership-accept.model.ts b/shared/models/videos/change-ownership/video-change-ownership-accept.model.ts
index f27247633..f27247633 100644
--- a/shared/models/videos/video-change-ownership-accept.model.ts
+++ b/shared/models/videos/change-ownership/video-change-ownership-accept.model.ts
diff --git a/shared/models/videos/video-change-ownership-create.model.ts b/shared/models/videos/change-ownership/video-change-ownership-create.model.ts
index 40fcca285..40fcca285 100644
--- a/shared/models/videos/video-change-ownership-create.model.ts
+++ b/shared/models/videos/change-ownership/video-change-ownership-create.model.ts
diff --git a/shared/models/videos/video-change-ownership.model.ts b/shared/models/videos/change-ownership/video-change-ownership.model.ts
index 669c7f3e7..3d31cad0a 100644
--- a/shared/models/videos/video-change-ownership.model.ts
+++ b/shared/models/videos/change-ownership/video-change-ownership.model.ts
@@ -1,5 +1,5 @@
1import { Account } from '../actors' 1import { Account } from '../../actors'
2import { Video } from './video.model' 2import { Video } from '../video.model'
3 3
4export interface VideoChangeOwnership { 4export interface VideoChangeOwnership {
5 id: number 5 id: number
diff --git a/shared/models/videos/comment/index.ts b/shared/models/videos/comment/index.ts
new file mode 100644
index 000000000..7b9261a36
--- /dev/null
+++ b/shared/models/videos/comment/index.ts
@@ -0,0 +1 @@
export * from './video-comment.model'
diff --git a/shared/models/videos/video-comment.model.ts b/shared/models/videos/comment/video-comment.model.ts
index 9730a3f76..79c0e4c0a 100644
--- a/shared/models/videos/video-comment.model.ts
+++ b/shared/models/videos/comment/video-comment.model.ts
@@ -1,4 +1,4 @@
1import { Account } from '../actors' 1import { Account } from '../../actors'
2 2
3export interface VideoComment { 3export interface VideoComment {
4 id: number 4 id: number
diff --git a/shared/models/videos/index.ts b/shared/models/videos/index.ts
index fac3e0b2f..64f2c9df6 100644
--- a/shared/models/videos/index.ts
+++ b/shared/models/videos/index.ts
@@ -1,6 +1,8 @@
1export * from './blacklist' 1export * from './blacklist'
2export * from './caption' 2export * from './caption'
3export * from './change-ownership'
3export * from './channel' 4export * from './channel'
5export * from './comment'
4export * from './live' 6export * from './live'
5export * from './import' 7export * from './import'
6export * from './playlist' 8export * from './playlist'
@@ -10,17 +12,11 @@ export * from './nsfw-policy.type'
10 12
11export * from './thumbnail.type' 13export * from './thumbnail.type'
12 14
13export * from './video-change-ownership-accept.model'
14export * from './video-change-ownership-create.model'
15export * from './video-change-ownership.model'
16
17export * from './video-comment.model'
18export * from './video-constant.model' 15export * from './video-constant.model'
19export * from './video-create.model' 16export * from './video-create.model'
20export * from './video-file-metadata'
21export * from './video-file.model'
22 17
23export * from './live/live-video.model' 18export * from './video-file-metadata.model'
19export * from './video-file.model'
24 20
25export * from './video-privacy.enum' 21export * from './video-privacy.enum'
26export * from './video-query.type' 22export * from './video-query.type'
diff --git a/shared/models/videos/video-file-metadata.ts b/shared/models/videos/video-file-metadata.model.ts
index 8f527c0a7..8f527c0a7 100644
--- a/shared/models/videos/video-file-metadata.ts
+++ b/shared/models/videos/video-file-metadata.model.ts
diff --git a/shared/models/videos/video-file.model.ts b/shared/models/videos/video-file.model.ts
index 1e830b19c..28fce0aaf 100644
--- a/shared/models/videos/video-file.model.ts
+++ b/shared/models/videos/video-file.model.ts
@@ -1,5 +1,5 @@
1import { VideoConstant } from './video-constant.model' 1import { VideoConstant } from './video-constant.model'
2import { VideoFileMetadata } from './video-file-metadata' 2import { VideoFileMetadata } from './video-file-metadata.model'
3import { VideoResolution } from './video-resolution.enum' 3import { VideoResolution } from './video-resolution.enum'
4 4
5export interface VideoFile { 5export interface VideoFile {
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml
index 0e0d2ab5f..fc62f95bb 100644
--- a/support/doc/api/openapi.yaml
+++ b/support/doc/api/openapi.yaml
@@ -4,12 +4,12 @@ info:
4 version: 3.2.0-rc.1 4 version: 3.2.0-rc.1
5 contact: 5 contact:
6 name: PeerTube Community 6 name: PeerTube Community
7 url: 'https://joinpeertube.org' 7 url: https://joinpeertube.org
8 license: 8 license:
9 name: AGPLv3.0 9 name: AGPLv3.0
10 url: 'https://github.com/Chocobozzz/PeerTube/blob/master/LICENSE' 10 url: https://github.com/Chocobozzz/PeerTube/blob/master/LICENSE
11 x-logo: 11 x-logo:
12 url: 'https://joinpeertube.org/img/brand.png' 12 url: https://joinpeertube.org/img/brand.png
13 altText: PeerTube Project Homepage 13 altText: PeerTube Project Homepage
14 description: | 14 description: |
15 The PeerTube API is built on HTTP(S) and is RESTful. You can use your favorite 15 The PeerTube API is built on HTTP(S) and is RESTful. You can use your favorite
@@ -27,8 +27,8 @@ info:
27 # Authentication 27 # Authentication
28 28
29 When you sign up for an account on a PeerTube instance, you are given the possibility 29 When you sign up for an account on a PeerTube instance, you are given the possibility
30 to generate sessions on it, and authenticate there using a session token. Only __one 30 to generate sessions on it, and authenticate there using an access token. Only __one
31 session token can currently be used at a time__. 31 access token can currently be used at a time__.
32 32
33 ## Roles 33 ## Roles
34 34
@@ -38,41 +38,60 @@ info:
38 # Errors 38 # Errors
39 39
40 The API uses standard HTTP status codes to indicate the success or failure 40 The API uses standard HTTP status codes to indicate the success or failure
41 of the API call. The body of the response will be JSON in the following 41 of the API call.
42 formats.
43 42
44 ``` 43 ```
44 HTTP 1.1 404 Not Found
45 Content-Type: application/json
46
45 { 47 {
46 "error": "Account not found" // error debug message 48 "errorCode": 1
49 "error": "Account not found"
47 } 50 }
48 ``` 51 ```
49 52
50 Some errors benefit from a more detailed message: 53 We provide error codes for [a growing number of cases](https://github.com/Chocobozzz/PeerTube/blob/develop/shared/models/server/server-error-code.enum.ts),
54 but it is still optional.
55
56 ### Validation errors
57
58 Each parameter is evaluated on its own against a set of rules before the route validator
59 proceeds with potential testing involving parameter combinations. Errors coming from Validation
60 errors appear earlier and benefit from a more detailed error type:
61
51 ``` 62 ```
63 HTTP 1.1 400 Bad Request
64 Content-Type: application/json
65
52 { 66 {
53 "errors": { 67 "errors": {
54 "id": { // where 'id' is the name of the parameter concerned by the error. 68 "id": { //
55 "value": "a117eb-c6a9-4756-bb09-2a956239f", // value that triggered the error. 69 "value": "a117eb-c6a9-4756-bb09-2a956239f",
56 "msg": "Should have an valid id", // error debug message 70 "msg": "Should have a valid id",
57 "param": "id", 71 "param": "id",
58 "location": "params" // 'params', 'body', 'header', 'query' or 'cookies' 72 "location": "params"
59 } 73 }
60 } 74 }
61 } 75 }
62 ``` 76 ```
63 77
78 Where `id` is the name of the field concerned by the error, within the route definition.
79 `errors.<field>.location` can be either 'params', 'body', 'header', 'query' or 'cookies', and
80 `errors.<field>.value` reports the value that didn't pass validation whose `errors.<field>.msg`
81 is about.
82
64 # Rate limits 83 # Rate limits
65 84
66 We are rate-limiting all endpoints of PeerTube's API. Custom values can be set by administrators: 85 We are rate-limiting all endpoints of PeerTube's API. Custom values can be set by administrators:
67 86
68 | Endpoint | Calls | Time frame | 87 | Endpoint (prefix: `/api/v1`) | Calls | Time frame |
69 |-------------------------|------------------|---------------------------| 88 |------------------------------|---------------|--------------|
70 | `/*` | 50 | 10 seconds | 89 | `/*` | 50 | 10 seconds |
71 | `POST /users/token` | 15 | 5 minutes | 90 | `POST /users/token` | 15 | 5 minutes |
72 | `POST /users/register` | 2¹ | 5 minutes | 91 | `POST /users/register` | 2<sup>*</sup> | 5 minutes |
73 | `POST /users/ask-send-verify-email` | 3 | 5 minutes | 92 | `POST /users/ask-send-verify-email` | 3 | 5 minutes |
74 93
75 Depending on the endpoint, ¹failed requests are not taken into account. A service 94 Depending on the endpoint, <sup>*</sup>failed requests are not taken into account. A service
76 limit is announced by a `429 Too Many Requests` status code. 95 limit is announced by a `429 Too Many Requests` status code.
77 96
78 You can get details about the current state of your rate limit by reading the 97 You can get details about the current state of your rate limit by reading the
@@ -80,13 +99,37 @@ info:
80 99
81 | Header | Description | 100 | Header | Description |
82 |-------------------------|------------------------------------------------------------| 101 |-------------------------|------------------------------------------------------------|
83 | X-RateLimit-Limit | Number of max requests allowed in the current time period | 102 | `X-RateLimit-Limit` | Number of max requests allowed in the current time period |
84 | X-RateLimit-Remaining | Number of remaining requests in the current time period | 103 | `X-RateLimit-Remaining` | Number of remaining requests in the current time period |
85 | X-RateLimit-Reset | Timestamp of end of current time period as UNIX timestamp | 104 | `X-RateLimit-Reset` | Timestamp of end of current time period as UNIX timestamp |
86 | Retry-After | Seconds to delay after the first `429` is received | 105 | `Retry-After` | Seconds to delay after the first `429` is received |
106
107 # CORS
108
109 This API features [Cross-Origin Resource Sharing (CORS)](https://fetch.spec.whatwg.org/),
110 allowing cross-domain communication from the browser for some routes:
111
112 | Endpoint |
113 |------------------------- ---|
114 | `/api/*` |
115 | `/download/*` |
116 | `/lazy-static/*` |
117 | `/live/segments-sha256/*` |
118 | `/.well-known/webfinger` |
119
120 In addition, all routes serving ActivityPub are CORS-enabled for all origins.
87externalDocs: 121externalDocs:
88 url: https://docs.joinpeertube.org/api-rest-reference.html 122 url: https://docs.joinpeertube.org/api-rest-reference.html
89tags: 123tags:
124 - name: Register
125 description: |
126 As a visitor, you can use this API to open an account (if registrations are open on
127 that PeerTube instance). As an admin, you should use the dedicated [User creation
128 API](#operation/createUser) instead.
129 - name: Session
130 x-displayName: Login/Logout
131 description: |
132 Sessions deal with access tokens over time. Only __one session token can currently be used at a time__.
90 - name: Accounts 133 - name: Accounts
91 description: > 134 description: >
92 Accounts encompass remote accounts discovered across the federation, 135 Accounts encompass remote accounts discovered across the federation,
@@ -210,6 +253,10 @@ tags:
210 253
211 For importing videos as your own, refer to [video imports](#operation/importVideo). 254 For importing videos as your own, refer to [video imports](#operation/importVideo).
212x-tagGroups: 255x-tagGroups:
256 - name: Auth
257 tags:
258 - Register
259 - Session
213 - name: Accounts 260 - name: Accounts
214 tags: 261 tags:
215 - Accounts 262 - Accounts
@@ -458,66 +505,105 @@ paths:
458 maxItems: 100 505 maxItems: 100
459 items: 506 items:
460 $ref: '#/components/schemas/Job' 507 $ref: '#/components/schemas/Job'
461 '/server/following/{host}': 508
509 /server/followers:
510 get:
511 tags:
512 - Instance Follows
513 summary: List instances following the server
514 parameters:
515 - $ref: '#/components/parameters/followState'
516 - $ref: '#/components/parameters/actorType'
517 - $ref: '#/components/parameters/start'
518 - $ref: '#/components/parameters/count'
519 - $ref: '#/components/parameters/sort'
520 responses:
521 '200':
522 description: successful operation
523 content:
524 application/json:
525 schema:
526 type: object
527 properties:
528 total:
529 type: integer
530 example: 1
531 data:
532 type: array
533 items:
534 $ref: '#/components/schemas/Follow'
535 '/server/followers/{nameWithHost}':
462 delete: 536 delete:
537 summary: Remove or reject a follower to your server
463 security: 538 security:
464 - OAuth2: 539 - OAuth2:
465 - admin 540 - admin
466 tags: 541 tags:
467 - Instance Follows 542 - Instance Follows
468 summary: Unfollow a server
469 parameters: 543 parameters:
470 - name: host 544 - name: nameWithHost
471 in: path 545 in: path
472 required: true 546 required: true
473 description: 'The host to unfollow ' 547 description: The remote actor handle to remove from your followers
474 schema: 548 schema:
475 type: string 549 type: string
476 format: hostname 550 format: email
477 responses: 551 responses:
478 '201': 552 '204':
479 description: successful operation 553 description: successful operation
480 /server/followers: 554 '404':
481 get: 555 description: follower not found
556 '/server/followers/{nameWithHost}/reject':
557 post:
558 summary: Reject a pending follower to your server
559 security:
560 - OAuth2:
561 - admin
482 tags: 562 tags:
483 - Instance Follows 563 - Instance Follows
484 summary: List instance followers
485 parameters: 564 parameters:
486 - $ref: '#/components/parameters/start' 565 - name: nameWithHost
487 - $ref: '#/components/parameters/count' 566 in: path
488 - $ref: '#/components/parameters/sort' 567 required: true
568 description: The remote actor handle to remove from your followers
569 schema:
570 type: string
571 format: email
489 responses: 572 responses:
490 '200': 573 '204':
491 description: successful operation 574 description: successful operation
492 content: 575 '404':
493 application/json: 576 description: follower not found
494 schema: 577 '/server/followers/{nameWithHost}/accept':
495 type: array 578 post:
496 items: 579 summary: Accept a pending follower to your server
497 $ref: '#/components/schemas/Follow' 580 security:
581 - OAuth2:
582 - admin
583 tags:
584 - Instance Follows
585 parameters:
586 - name: nameWithHost
587 in: path
588 required: true
589 description: The remote actor handle to remove from your followers
590 schema:
591 type: string
592 format: email
593 responses:
594 '204':
595 description: successful operation
596 '404':
597 description: follower not found
598
498 /server/following: 599 /server/following:
499 get: 600 get:
500 tags: 601 tags:
501 - Instance Follows 602 - Instance Follows
502 summary: List instances followed by the server 603 summary: List instances followed by the server
503 parameters: 604 parameters:
504 - name: state 605 - $ref: '#/components/parameters/followState'
505 in: query 606 - $ref: '#/components/parameters/actorType'
506 schema:
507 type: string
508 enum:
509 - pending
510 - accepted
511 - name: actorType
512 in: query
513 schema:
514 type: string
515 enum:
516 - Person
517 - Application
518 - Group
519 - Service
520 - Organization
521 - $ref: '#/components/parameters/start' 607 - $ref: '#/components/parameters/start'
522 - $ref: '#/components/parameters/count' 608 - $ref: '#/components/parameters/count'
523 - $ref: '#/components/parameters/sort' 609 - $ref: '#/components/parameters/sort'
@@ -527,16 +613,22 @@ paths:
527 content: 613 content:
528 application/json: 614 application/json:
529 schema: 615 schema:
530 type: array 616 type: object
531 items: 617 properties:
532 $ref: '#/components/schemas/Follow' 618 total:
619 type: integer
620 example: 1
621 data:
622 type: array
623 items:
624 $ref: '#/components/schemas/Follow'
533 post: 625 post:
534 security: 626 security:
535 - OAuth2: 627 - OAuth2:
536 - admin 628 - admin
537 tags: 629 tags:
538 - Instance Follows 630 - Instance Follows
539 summary: Follow a server 631 summary: Follow a list of servers
540 responses: 632 responses:
541 '204': 633 '204':
542 description: successful operation 634 description: successful operation
@@ -554,9 +646,32 @@ paths:
554 type: string 646 type: string
555 format: hostname 647 format: hostname
556 uniqueItems: true 648 uniqueItems: true
649 '/server/following/{host}':
650 delete:
651 summary: Unfollow a server
652 security:
653 - OAuth2:
654 - admin
655 tags:
656 - Instance Follows
657 parameters:
658 - name: host
659 in: path
660 required: true
661 description: The host to unfollow
662 schema:
663 type: string
664 format: hostname
665 responses:
666 '204':
667 description: successful operation
668 '404':
669 description: host not found
670
557 /users: 671 /users:
558 post: 672 post:
559 summary: Create a user 673 summary: Create a user
674 operationId: createUser
560 security: 675 security:
561 - OAuth2: 676 - OAuth2:
562 - admin 677 - admin
@@ -673,11 +788,92 @@ paths:
673 schema: 788 schema:
674 $ref: '#/components/schemas/UpdateUser' 789 $ref: '#/components/schemas/UpdateUser'
675 required: true 790 required: true
791
792 /oauth-clients/local:
793 get:
794 summary: Login prerequisite
795 description: You need to retrieve a client id and secret before [logging in](#operation/getOauthToken).
796 operationId: getOAuthClient
797 tags:
798 - Session
799 responses:
800 '200':
801 description: successful operation
802 content:
803 application/json:
804 schema:
805 $ref: '#/components/schemas/OAuthClient'
806 links:
807 UseOAuthClientToLogin:
808 operationId: getOAuthToken
809 parameters:
810 client_id: '$response.body#/client_id'
811 client_secret: '$response.body#/client_secret'
812 /users/token:
813 post:
814 summary: Login
815 operationId: getOAuthToken
816 description: With your [client id and secret](#operation/getOAuthClient), you can retrieve an access and refresh tokens.
817 tags:
818 - Session
819 requestBody:
820 content:
821 application/x-www-form-urlencoded:
822 schema:
823 oneOf:
824 - $ref: '#/components/schemas/OAuthToken-password'
825 - $ref: '#/components/schemas/OAuthToken-refresh_token'
826 discriminator:
827 propertyName: grant_type
828 mapping:
829 password: '#/components/schemas/OAuthToken-password'
830 refresh_token: '#/components/schemas/OAuthToken-refresh_token'
831 responses:
832 '200':
833 description: successful operation
834 content:
835 application/json:
836 schema:
837 type: object
838 properties:
839 token_type:
840 type: string
841 example: Bearer
842 access_token:
843 type: string
844 example: 90286a0bdf0f7315d9d3fe8dabf9e1d2be9c97d0
845 description: valid for 1 day
846 refresh_token:
847 type: string
848 example: 2e0d675df9fc96d2e4ec8a3ebbbf45eca9137bb7
849 description: valid for 2 weeks
850 expires_in:
851 type: integer
852 minimum: 0
853 example: 14399
854 refresh_token_expires_in:
855 type: integer
856 minimum: 0
857 example: 1209600
858 /users/revoke-token:
859 post:
860 summary: Logout
861 description: Revokes your access token and its associated refresh token, destroying your current session.
862 operationId: revokeOAuthToken
863 tags:
864 - Session
865 security:
866 - OAuth2: []
867 responses:
868 '200':
869 description: successful operation
870
676 /users/register: 871 /users/register:
677 post: 872 post:
678 summary: Register a user 873 summary: Register a user
679 tags: 874 tags:
680 - Users 875 - Users
876 - Register
681 responses: 877 responses:
682 '204': 878 '204':
683 description: successful operation 879 description: successful operation
@@ -687,6 +883,47 @@ paths:
687 schema: 883 schema:
688 $ref: '#/components/schemas/RegisterUser' 884 $ref: '#/components/schemas/RegisterUser'
689 required: true 885 required: true
886 /users/{id}/verify-email:
887 post:
888 summary: Verify a user
889 description: |
890 Following a user registration, the new user will receive an email asking to click a link
891 containing a secret.
892 tags:
893 - Users
894 - Register
895 parameters:
896 - $ref: '#/components/parameters/id'
897 requestBody:
898 content:
899 application/json:
900 schema:
901 type: object
902 properties:
903 verificationString:
904 type: string
905 format: url
906 isPendingEmail:
907 type: boolean
908 required:
909 - verificationString
910 responses:
911 '204':
912 description: successful operation
913 '403':
914 description: invalid verification string
915 '404':
916 description: user not found
917 /users/ask-send-verify-email:
918 post:
919 summary: Resend user verification link
920 tags:
921 - Users
922 - Register
923 responses:
924 '204':
925 description: successful operation
926
690 /users/me: 927 /users/me:
691 get: 928 get:
692 summary: Get my user information 929 summary: Get my user information
@@ -1317,7 +1554,7 @@ paths:
1317 type: string 1554 type: string
1318 support: 1555 support:
1319 description: A text tell the audience how to support the video creator 1556 description: A text tell the audience how to support the video creator
1320 example: Please support my work on <insert crowdfunding plateform>! <3 1557 example: Please support our work on https://soutenir.framasoft.org/en/ <3
1321 type: string 1558 type: string
1322 nsfw: 1559 nsfw:
1323 description: Whether or not this video contains sensitive content 1560 description: Whether or not this video contains sensitive content
@@ -1672,74 +1909,7 @@ paths:
1672 content: 1909 content:
1673 multipart/form-data: 1910 multipart/form-data:
1674 schema: 1911 schema:
1675 type: object 1912 $ref: '#/components/schemas/VideoCreateImport'
1676 properties:
1677 torrentfile:
1678 description: Torrent File
1679 type: string
1680 format: binary
1681 targetUrl:
1682 $ref: '#/components/schemas/VideoImport/properties/targetUrl'
1683 magnetUri:
1684 $ref: '#/components/schemas/VideoImport/properties/magnetUri'
1685 channelId:
1686 description: Channel id that will contain this video
1687 allOf:
1688 - $ref: '#/components/schemas/VideoChannel/properties/id'
1689 thumbnailfile:
1690 description: Video thumbnail file
1691 type: string
1692 format: binary
1693 previewfile:
1694 description: Video preview file
1695 type: string
1696 format: binary
1697 privacy:
1698 $ref: '#/components/schemas/VideoPrivacySet'
1699 category:
1700 $ref: '#/components/schemas/VideoCategorySet'
1701 licence:
1702 $ref: '#/components/schemas/VideoLicenceSet'
1703 language:
1704 $ref: '#/components/schemas/VideoLanguageSet'
1705 description:
1706 description: Video description
1707 type: string
1708 waitTranscoding:
1709 description: Whether or not we wait transcoding before publish the video
1710 type: boolean
1711 support:
1712 description: A text tell the audience how to support the video creator
1713 example: Please support my work on <insert crowdfunding plateform>! <3
1714 type: string
1715 nsfw:
1716 description: Whether or not this video contains sensitive content
1717 type: boolean
1718 name:
1719 description: Video name
1720 type: string
1721 minLength: 3
1722 maxLength: 120
1723 tags:
1724 description: Video tags (maximum 5 tags each between 2 and 30 characters)
1725 type: array
1726 minItems: 1
1727 maxItems: 5
1728 items:
1729 type: string
1730 minLength: 2
1731 maxLength: 30
1732 commentsEnabled:
1733 description: Enable or disable comments for this video
1734 type: boolean
1735 downloadEnabled:
1736 description: Enable or disable downloading for this video
1737 type: boolean
1738 scheduleUpdate:
1739 $ref: '#/components/schemas/VideoScheduledUpdate'
1740 required:
1741 - channelId
1742 - name
1743 encoding: 1913 encoding:
1744 torrentfile: 1914 torrentfile:
1745 contentType: application/x-bittorrent 1915 contentType: application/x-bittorrent
@@ -1814,7 +1984,7 @@ paths:
1814 type: string 1984 type: string
1815 support: 1985 support:
1816 description: A text tell the audience how to support the creator 1986 description: A text tell the audience how to support the creator
1817 example: Please support my work on <insert crowdfunding plateform>! <3 1987 example: Please support our work on https://soutenir.framasoft.org/en/ <3
1818 type: string 1988 type: string
1819 nsfw: 1989 nsfw:
1820 description: Whether or not this live video/replay contains sensitive content 1990 description: Whether or not this live video/replay contains sensitive content
@@ -2042,10 +2212,12 @@ paths:
2042 - $ref: '#/components/schemas/Video/properties/id' 2212 - $ref: '#/components/schemas/Video/properties/id'
2043 startAt: 2213 startAt:
2044 type: integer 2214 type: integer
2215 format: seconds
2045 description: Timestamp in the video that marks the beginning of the report 2216 description: Timestamp in the video that marks the beginning of the report
2046 minimum: 0 2217 minimum: 0
2047 endAt: 2218 endAt:
2048 type: integer 2219 type: integer
2220 format: seconds
2049 description: Timestamp in the video that marks the ending of the report 2221 description: Timestamp in the video that marks the ending of the report
2050 minimum: 0 2222 minimum: 0
2051 comment: 2223 comment:
@@ -2064,8 +2236,18 @@ paths:
2064 required: 2236 required:
2065 - reason 2237 - reason
2066 responses: 2238 responses:
2067 '204': 2239 '200':
2068 description: successful operation 2240 description: successful operation
2241 content:
2242 application/json:
2243 schema:
2244 type: object
2245 properties:
2246 abuse:
2247 type: object
2248 properties:
2249 id:
2250 $ref: '#/components/schemas/id'
2069 '400': 2251 '400':
2070 description: incorrect request parameters 2252 description: incorrect request parameters
2071 '/abuses/{abuseId}': 2253 '/abuses/{abuseId}':
@@ -2127,9 +2309,15 @@ paths:
2127 content: 2309 content:
2128 application/json: 2310 application/json:
2129 schema: 2311 schema:
2130 type: array 2312 type: object
2131 items: 2313 properties:
2132 $ref: '#/components/schemas/AbuseMessage' 2314 total:
2315 type: integer
2316 example: 1
2317 data:
2318 type: array
2319 items:
2320 $ref: '#/components/schemas/AbuseMessage'
2133 2321
2134 post: 2322 post:
2135 summary: Add message to an abuse 2323 summary: Add message to an abuse
@@ -2316,6 +2504,7 @@ paths:
2316 /video-channels: 2504 /video-channels:
2317 get: 2505 get:
2318 summary: List video channels 2506 summary: List video channels
2507 operationId: getVideoChannels
2319 tags: 2508 tags:
2320 - Video Channels 2509 - Video Channels
2321 parameters: 2510 parameters:
@@ -2331,6 +2520,7 @@ paths:
2331 $ref: '#/components/schemas/VideoChannelList' 2520 $ref: '#/components/schemas/VideoChannelList'
2332 post: 2521 post:
2333 summary: Create a video channel 2522 summary: Create a video channel
2523 operationId: createVideoChannel
2334 security: 2524 security:
2335 - OAuth2: [] 2525 - OAuth2: []
2336 tags: 2526 tags:
@@ -2338,6 +2528,16 @@ paths:
2338 responses: 2528 responses:
2339 '204': 2529 '204':
2340 description: successful operation 2530 description: successful operation
2531 content:
2532 application/json:
2533 schema:
2534 type: object
2535 properties:
2536 videoChannel:
2537 type: object
2538 properties:
2539 id:
2540 $ref: '#/components/schemas/VideoChannel/properties/id'
2341 requestBody: 2541 requestBody:
2342 content: 2542 content:
2343 application/json: 2543 application/json:
@@ -2346,6 +2546,7 @@ paths:
2346 '/video-channels/{channelHandle}': 2546 '/video-channels/{channelHandle}':
2347 get: 2547 get:
2348 summary: Get a video channel 2548 summary: Get a video channel
2549 operationId: getVideoChannel
2349 tags: 2550 tags:
2350 - Video Channels 2551 - Video Channels
2351 parameters: 2552 parameters:
@@ -2617,13 +2818,13 @@ paths:
2617 thumbnailfile: 2818 thumbnailfile:
2618 contentType: image/jpeg 2819 contentType: image/jpeg
2619 2820
2620 /video-playlists/{id}: 2821 /video-playlists/{playlistId}:
2621 get: 2822 get:
2622 summary: Get a video playlist 2823 summary: Get a video playlist
2623 tags: 2824 tags:
2624 - Video Playlists 2825 - Video Playlists
2625 parameters: 2826 parameters:
2626 - $ref: '#/components/parameters/idOrUUID' 2827 - $ref: '#/components/parameters/playlistId'
2627 responses: 2828 responses:
2628 '200': 2829 '200':
2629 description: successful operation 2830 description: successful operation
@@ -2642,7 +2843,7 @@ paths:
2642 '204': 2843 '204':
2643 description: successful operation 2844 description: successful operation
2644 parameters: 2845 parameters:
2645 - $ref: '#/components/parameters/idOrUUID' 2846 - $ref: '#/components/parameters/playlistId'
2646 requestBody: 2847 requestBody:
2647 content: 2848 content:
2648 multipart/form-data: 2849 multipart/form-data:
@@ -2677,19 +2878,19 @@ paths:
2677 tags: 2878 tags:
2678 - Video Playlists 2879 - Video Playlists
2679 parameters: 2880 parameters:
2680 - $ref: '#/components/parameters/idOrUUID' 2881 - $ref: '#/components/parameters/playlistId'
2681 responses: 2882 responses:
2682 '204': 2883 '204':
2683 description: successful operation 2884 description: successful operation
2684 2885
2685 /video-playlists/{id}/videos: 2886 /video-playlists/{playlistId}/videos:
2686 get: 2887 get:
2687 summary: 'List videos of a playlist' 2888 summary: 'List videos of a playlist'
2688 tags: 2889 tags:
2689 - Videos 2890 - Videos
2690 - Video Playlists 2891 - Video Playlists
2691 parameters: 2892 parameters:
2692 - $ref: '#/components/parameters/idOrUUID' 2893 - $ref: '#/components/parameters/playlistId'
2693 responses: 2894 responses:
2694 '200': 2895 '200':
2695 description: successful operation 2896 description: successful operation
@@ -2698,14 +2899,14 @@ paths:
2698 schema: 2899 schema:
2699 $ref: '#/components/schemas/VideoListResponse' 2900 $ref: '#/components/schemas/VideoListResponse'
2700 post: 2901 post:
2701 summary: 'Add a video in a playlist' 2902 summary: Add a video in a playlist
2702 security: 2903 security:
2703 - OAuth2: [] 2904 - OAuth2: []
2704 tags: 2905 tags:
2705 - Videos 2906 - Videos
2706 - Video Playlists 2907 - Video Playlists
2707 parameters: 2908 parameters:
2708 - $ref: '#/components/parameters/idOrUUID' 2909 - $ref: '#/components/parameters/playlistId'
2709 responses: 2910 responses:
2710 '200': 2911 '200':
2711 description: successful operation 2912 description: successful operation
@@ -2719,6 +2920,7 @@ paths:
2719 properties: 2920 properties:
2720 id: 2921 id:
2721 type: integer 2922 type: integer
2923 example: 2
2722 requestBody: 2924 requestBody:
2723 content: 2925 content:
2724 application/json: 2926 application/json:
@@ -2726,19 +2928,22 @@ paths:
2726 type: object 2928 type: object
2727 properties: 2929 properties:
2728 videoId: 2930 videoId:
2729 allOf: 2931 oneOf:
2932 - $ref: '#/components/schemas/Video/properties/uuid'
2730 - $ref: '#/components/schemas/Video/properties/id' 2933 - $ref: '#/components/schemas/Video/properties/id'
2731 description: Video to add in the playlist 2934 description: Video to add in the playlist
2732 startTimestamp: 2935 startTimestamp:
2733 type: integer 2936 type: integer
2734 description: Start the video at this specific timestamp (in seconds) 2937 format: seconds
2938 description: Start the video at this specific timestamp
2735 stopTimestamp: 2939 stopTimestamp:
2736 type: integer 2940 type: integer
2737 description: Stop the video at this specific timestamp (in seconds) 2941 format: seconds
2942 description: Stop the video at this specific timestamp
2738 required: 2943 required:
2739 - videoId 2944 - videoId
2740 2945
2741 /video-playlists/{id}/videos/reorder: 2946 /video-playlists/{playlistId}/videos/reorder:
2742 post: 2947 post:
2743 summary: 'Reorder a playlist' 2948 summary: 'Reorder a playlist'
2744 security: 2949 security:
@@ -2746,7 +2951,7 @@ paths:
2746 tags: 2951 tags:
2747 - Video Playlists 2952 - Video Playlists
2748 parameters: 2953 parameters:
2749 - $ref: '#/components/parameters/idOrUUID' 2954 - $ref: '#/components/parameters/playlistId'
2750 responses: 2955 responses:
2751 '204': 2956 '204':
2752 description: successful operation 2957 description: successful operation
@@ -2772,15 +2977,15 @@ paths:
2772 - startPosition 2977 - startPosition
2773 - insertAfterPosition 2978 - insertAfterPosition
2774 2979
2775 /video-playlists/{id}/videos/{playlistElementId}: 2980 /video-playlists/{playlistId}/videos/{playlistElementId}:
2776 put: 2981 put:
2777 summary: 'Update a playlist element' 2982 summary: Update a playlist element
2778 security: 2983 security:
2779 - OAuth2: [] 2984 - OAuth2: []
2780 tags: 2985 tags:
2781 - Video Playlists 2986 - Video Playlists
2782 parameters: 2987 parameters:
2783 - $ref: '#/components/parameters/idOrUUID' 2988 - $ref: '#/components/parameters/playlistId'
2784 - $ref: '#/components/parameters/playlistElementId' 2989 - $ref: '#/components/parameters/playlistElementId'
2785 responses: 2990 responses:
2786 '204': 2991 '204':
@@ -2793,18 +2998,20 @@ paths:
2793 properties: 2998 properties:
2794 startTimestamp: 2999 startTimestamp:
2795 type: integer 3000 type: integer
2796 description: 'Start the video at this specific timestamp (in seconds)' 3001 format: seconds
3002 description: Start the video at this specific timestamp
2797 stopTimestamp: 3003 stopTimestamp:
2798 type: integer 3004 type: integer
2799 description: 'Stop the video at this specific timestamp (in seconds)' 3005 format: seconds
3006 description: Stop the video at this specific timestamp
2800 delete: 3007 delete:
2801 summary: 'Delete an element from a playlist' 3008 summary: Delete an element from a playlist
2802 security: 3009 security:
2803 - OAuth2: [] 3010 - OAuth2: []
2804 tags: 3011 tags:
2805 - Video Playlists 3012 - Video Playlists
2806 parameters: 3013 parameters:
2807 - $ref: '#/components/parameters/idOrUUID' 3014 - $ref: '#/components/parameters/playlistId'
2808 - $ref: '#/components/parameters/playlistElementId' 3015 - $ref: '#/components/parameters/playlistElementId'
2809 responses: 3016 responses:
2810 '204': 3017 '204':
@@ -2812,7 +3019,7 @@ paths:
2812 3019
2813 '/users/me/video-playlists/videos-exist': 3020 '/users/me/video-playlists/videos-exist':
2814 get: 3021 get:
2815 summary: 'Check video exists in my playlists' 3022 summary: Check video exists in my playlists
2816 security: 3023 security:
2817 - OAuth2: [] 3024 - OAuth2: []
2818 tags: 3025 tags:
@@ -2845,8 +3052,10 @@ paths:
2845 type: integer 3052 type: integer
2846 startTimestamp: 3053 startTimestamp:
2847 type: integer 3054 type: integer
3055 format: seconds
2848 stopTimestamp: 3056 stopTimestamp:
2849 type: integer 3057 type: integer
3058 format: seconds
2850 3059
2851 '/accounts/{name}/video-channels': 3060 '/accounts/{name}/video-channels':
2852 get: 3061 get:
@@ -2942,8 +3151,10 @@ paths:
2942 type: object 3151 type: object
2943 properties: 3152 properties:
2944 text: 3153 text:
2945 type: string 3154 allOf:
2946 description: 'Text comment' 3155 - $ref: '#/components/schemas/VideoComment/properties/text'
3156 format: markdown
3157 maxLength: 10000
2947 required: 3158 required:
2948 - text 3159 - text
2949 3160
@@ -2988,7 +3199,10 @@ paths:
2988 type: object 3199 type: object
2989 properties: 3200 properties:
2990 text: 3201 text:
2991 $ref: '#/components/schemas/VideoComment/properties/text' 3202 allOf:
3203 - $ref: '#/components/schemas/VideoComment/properties/text'
3204 format: markdown
3205 maxLength: 10000
2992 required: 3206 required:
2993 - text 3207 - text
2994 3208
@@ -3019,6 +3233,19 @@ paths:
3019 - Video Rates 3233 - Video Rates
3020 parameters: 3234 parameters:
3021 - $ref: '#/components/parameters/idOrUUID' 3235 - $ref: '#/components/parameters/idOrUUID'
3236 requestBody:
3237 content:
3238 application/json:
3239 schema:
3240 type: object
3241 properties:
3242 rating:
3243 type: string
3244 enum:
3245 - like
3246 - dislike
3247 required:
3248 - rating
3022 responses: 3249 responses:
3023 '204': 3250 '204':
3024 description: successful operation 3251 description: successful operation
@@ -3130,7 +3357,8 @@ paths:
3130 $ref: '#/components/schemas/VideoChannelList' 3357 $ref: '#/components/schemas/VideoChannelList'
3131 '500': 3358 '500':
3132 description: search index unavailable 3359 description: search index unavailable
3133 /blocklist/accounts: 3360
3361 /server/blocklist/accounts:
3134 get: 3362 get:
3135 tags: 3363 tags:
3136 - Account Blocks 3364 - Account Blocks
@@ -3169,7 +3397,7 @@ paths:
3169 description: successful operation 3397 description: successful operation
3170 '409': 3398 '409':
3171 description: self-blocking forbidden 3399 description: self-blocking forbidden
3172 '/blocklist/accounts/{accountName}': 3400 '/server/blocklist/accounts/{accountName}':
3173 delete: 3401 delete:
3174 tags: 3402 tags:
3175 - Account Blocks 3403 - Account Blocks
@@ -3189,7 +3417,8 @@ paths:
3189 description: successful operation 3417 description: successful operation
3190 '404': 3418 '404':
3191 description: account or account block does not exist 3419 description: account or account block does not exist
3192 /blocklist/servers: 3420
3421 /server/blocklist/servers:
3193 get: 3422 get:
3194 tags: 3423 tags:
3195 - Server Blocks 3424 - Server Blocks
@@ -3224,11 +3453,11 @@ paths:
3224 required: 3453 required:
3225 - host 3454 - host
3226 responses: 3455 responses:
3227 '200': 3456 '204':
3228 description: successful operation 3457 description: successful operation
3229 '409': 3458 '409':
3230 description: self-blocking forbidden 3459 description: self-blocking forbidden
3231 '/blocklist/servers/{host}': 3460 '/server/blocklist/servers/{host}':
3232 delete: 3461 delete:
3233 tags: 3462 tags:
3234 - Server Blocks 3463 - Server Blocks
@@ -3245,11 +3474,12 @@ paths:
3245 type: string 3474 type: string
3246 format: hostname 3475 format: hostname
3247 responses: 3476 responses:
3248 '201': 3477 '204':
3249 description: successful operation 3478 description: successful operation
3250 '404': 3479 '404':
3251 description: account block does not exist 3480 description: account block does not exist
3252 /redundancy/{host}: 3481
3482 /server/redundancy/{host}:
3253 put: 3483 put:
3254 tags: 3484 tags:
3255 - Instance Redundancy 3485 - Instance Redundancy
@@ -3281,7 +3511,7 @@ paths:
3281 description: successful operation 3511 description: successful operation
3282 '404': 3512 '404':
3283 description: server is not already known 3513 description: server is not already known
3284 /redundancy/videos: 3514 /server/redundancy/videos:
3285 get: 3515 get:
3286 tags: 3516 tags:
3287 - Video Mirroring 3517 - Video Mirroring
@@ -3337,7 +3567,7 @@ paths:
3337 description: video does not exist 3567 description: video does not exist
3338 '409': 3568 '409':
3339 description: video is already mirrored 3569 description: video is already mirrored
3340 /redundancy/videos/{redundancyId}: 3570 /server/redundancy/videos/{redundancyId}:
3341 delete: 3571 delete:
3342 tags: 3572 tags:
3343 - Video Mirroring 3573 - Video Mirroring
@@ -3357,6 +3587,7 @@ paths:
3357 description: successful operation 3587 description: successful operation
3358 '404': 3588 '404':
3359 description: video redundancy not found 3589 description: video redundancy not found
3590
3360 '/feeds/video-comments.{format}': 3591 '/feeds/video-comments.{format}':
3361 get: 3592 get:
3362 tags: 3593 tags:
@@ -4019,6 +4250,13 @@ components:
4019 oneOf: 4250 oneOf:
4020 - $ref: '#/components/schemas/id' 4251 - $ref: '#/components/schemas/id'
4021 - $ref: '#/components/schemas/UUIDv4' 4252 - $ref: '#/components/schemas/UUIDv4'
4253 playlistId:
4254 name: playlistId
4255 in: path
4256 required: true
4257 description: Playlist id
4258 schema:
4259 $ref: '#/components/schemas/VideoPlaylist/properties/id'
4022 playlistElementId: 4260 playlistElementId:
4023 name: playlistElementId 4261 name: playlistElementId
4024 in: path 4262 in: path
@@ -4223,22 +4461,42 @@ components:
4223 - activitypub-refresher 4461 - activitypub-refresher
4224 - video-redundancy 4462 - video-redundancy
4225 - video-live-ending 4463 - video-live-ending
4464 followState:
4465 name: state
4466 in: query
4467 schema:
4468 type: string
4469 enum:
4470 - pending
4471 - accepted
4472 actorType:
4473 name: actorType
4474 in: query
4475 schema:
4476 type: string
4477 enum:
4478 - Person
4479 - Application
4480 - Group
4481 - Service
4482 - Organization
4226 securitySchemes: 4483 securitySchemes:
4227 OAuth2: 4484 OAuth2:
4228 description: | 4485 description: |
4229 Authenticating via OAuth requires the following steps: 4486 Authenticating via OAuth requires the following steps:
4230 - Have an activated account 4487 - Have an activated account
4231 - [Generate](https://docs.joinpeertube.org/api-rest-getting-started) a 4488 - [Generate] an access token for that account at `/api/v1/users/token`.
4232 Bearer Token for that account at `/api/v1/users/token` 4489 - Make requests with the *Authorization: Bearer <token\>* header
4233 - Make authenticated requests, putting *Authorization: Bearer <token\>*
4234 - Profit, depending on the role assigned to the account 4490 - Profit, depending on the role assigned to the account
4235 4491
4236 Note that the __access token is valid for 1 day__ and, and is given 4492 Note that the __access token is valid for 1 day__ and is given
4237 along with a __refresh token valid for 2 weeks__. 4493 along with a __refresh token valid for 2 weeks__.
4494
4495 [Generate]: https://docs.joinpeertube.org/api-rest-getting-started
4238 type: oauth2 4496 type: oauth2
4239 flows: 4497 flows:
4240 password: 4498 password:
4241 tokenUrl: 'https://peertube.example.com/api/v1/users/token' 4499 tokenUrl: /api/v1/users/token
4242 scopes: 4500 scopes:
4243 admin: Admin scope 4501 admin: Admin scope
4244 moderator: Moderator scope 4502 moderator: Moderator scope
@@ -4258,20 +4516,21 @@ components:
4258 maxLength: 36 4516 maxLength: 36
4259 username: 4517 username:
4260 type: string 4518 type: string
4261 description: The username of the user 4519 description: immutable name of the user, used to find or mention its actor
4262 example: chocobozzz 4520 example: chocobozzz
4263 pattern: '/^[a-z0-9._]{1,50}$/' 4521 pattern: '/^[a-z0-9._]+$/'
4264 minLength: 1 4522 minLength: 1
4265 maxLength: 50 4523 maxLength: 50
4266 usernameChannel: 4524 usernameChannel:
4267 type: string 4525 type: string
4268 description: The username for the default channel 4526 description: immutable name of the channel, used to interact with its actor
4269 example: The Capybara Channel 4527 example: framasoft_videos
4270 pattern: '/^[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]+$/' 4528 pattern: '/^[a-zA-Z0-9\\-_.:]+$/'
4529 minLength: 1
4530 maxLength: 50
4271 password: 4531 password:
4272 type: string 4532 type: string
4273 format: password 4533 format: password
4274 description: The password of the user
4275 minLength: 6 4534 minLength: 6
4276 maxLength: 255 4535 maxLength: 255
4277 4536
@@ -4483,8 +4742,10 @@ components:
4483 type: integer 4742 type: integer
4484 startTimestamp: 4743 startTimestamp:
4485 type: integer 4744 type: integer
4745 format: seconds
4486 stopTimestamp: 4746 stopTimestamp:
4487 type: integer 4747 type: integer
4748 format: seconds
4488 video: 4749 video:
4489 nullable: true 4750 nullable: true
4490 allOf: 4751 allOf:
@@ -4633,6 +4894,7 @@ components:
4633 duration: 4894 duration:
4634 type: integer 4895 type: integer
4635 example: 1419 4896 example: 1419
4897 format: seconds
4636 description: duration of the video in seconds 4898 description: duration of the video in seconds
4637 isLocal: 4899 isLocal:
4638 type: boolean 4900 type: boolean
@@ -4701,7 +4963,7 @@ components:
4701 support: 4963 support:
4702 type: string 4964 type: string
4703 description: A text tell the audience how to support the video creator 4965 description: A text tell the audience how to support the video creator
4704 example: Please support my work on <insert crowdfunding plateform>! <3 4966 example: Please support our work on https://soutenir.framasoft.org/en/ <3
4705 minLength: 3 4967 minLength: 3
4706 maxLength: 1000 4968 maxLength: 1000
4707 channel: 4969 channel:
@@ -4806,10 +5068,33 @@ components:
4806 label: 5068 label:
4807 type: string 5069 type: string
4808 example: Pending 5070 example: Pending
5071 VideoCreateImport:
5072 allOf:
5073 - type: object
5074 additionalProperties: false
5075 oneOf:
5076 - properties:
5077 targetUrl:
5078 $ref: '#/components/schemas/VideoImport/properties/targetUrl'
5079 required: [targetUrl]
5080 - properties:
5081 magnetUri:
5082 $ref: '#/components/schemas/VideoImport/properties/magnetUri'
5083 required: [magnetUri]
5084 - properties:
5085 torrentfile:
5086 $ref: '#/components/schemas/VideoImport/properties/torrentfile'
5087 required: [torrentfile]
5088 - $ref: '#/components/schemas/VideoUploadRequestCommon'
5089 required:
5090 - channelId
5091 - name
4809 VideoImport: 5092 VideoImport:
4810 properties: 5093 properties:
4811 id: 5094 id:
4812 $ref: '#/components/schemas/id' 5095 readOnly: true
5096 allOf:
5097 - $ref: '#/components/schemas/id'
4813 targetUrl: 5098 targetUrl:
4814 type: string 5099 type: string
4815 format: url 5100 format: url
@@ -4821,19 +5106,31 @@ components:
4821 description: magnet URI allowing to resolve the import's source video 5106 description: magnet URI allowing to resolve the import's source video
4822 example: magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F9c9de5e8-0a1e-484a-b099-e80766180a6d-240.torrent&xt=urn:btih:38b4747ff788b30bf61f59d1965cd38f9e48e01f&dn=What+is+PeerTube%3F&tr=wss%3A%2F%2Fframatube.org%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F9c9de5e8-0a1e-484a-b099-e80766180a6d-240.mp4 5107 example: magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F9c9de5e8-0a1e-484a-b099-e80766180a6d-240.torrent&xt=urn:btih:38b4747ff788b30bf61f59d1965cd38f9e48e01f&dn=What+is+PeerTube%3F&tr=wss%3A%2F%2Fframatube.org%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F9c9de5e8-0a1e-484a-b099-e80766180a6d-240.mp4
4823 pattern: /magnet:\?xt=urn:[a-z0-9]+:[a-z0-9]{32}/i 5108 pattern: /magnet:\?xt=urn:[a-z0-9]+:[a-z0-9]{32}/i
5109 torrentfile:
5110 writeOnly: true
5111 type: string
5112 format: binary
5113 description: Torrent file containing only the video file
4824 torrentName: 5114 torrentName:
5115 readOnly: true
4825 type: string 5116 type: string
4826 state: 5117 state:
4827 $ref: '#/components/schemas/VideoImportStateConstant' 5118 readOnly: true
5119 allOf:
5120 - $ref: '#/components/schemas/VideoImportStateConstant'
4828 error: 5121 error:
5122 readOnly: true
4829 type: string 5123 type: string
4830 createdAt: 5124 createdAt:
5125 readOnly: true
4831 type: string 5126 type: string
4832 format: date-time 5127 format: date-time
4833 updatedAt: 5128 updatedAt:
5129 readOnly: true
4834 type: string 5130 type: string
4835 format: date-time 5131 format: date-time
4836 video: 5132 video:
5133 readOnly: true
4837 nullable: true 5134 nullable: true
4838 allOf: 5135 allOf:
4839 - $ref: '#/components/schemas/Video' 5136 - $ref: '#/components/schemas/Video'
@@ -4963,13 +5260,16 @@ components:
4963 format: url 5260 format: url
4964 text: 5261 text:
4965 type: string 5262 type: string
4966 description: Text of the comment in Markdown 5263 format: html
5264 description: Text of the comment
4967 minLength: 1 5265 minLength: 1
4968 maxLength: 10000 5266 example: This video is wonderful!
4969 threadId: 5267 threadId:
4970 type: integer
4971 inReplyToCommentId:
4972 $ref: '#/components/schemas/id' 5268 $ref: '#/components/schemas/id'
5269 inReplyToCommentId:
5270 nullable: true
5271 allOf:
5272 - $ref: '#/components/schemas/id'
4973 videoId: 5273 videoId:
4974 $ref: '#/components/schemas/Video/properties/id' 5274 $ref: '#/components/schemas/Video/properties/id'
4975 createdAt: 5275 createdAt:
@@ -4978,6 +5278,14 @@ components:
4978 updatedAt: 5278 updatedAt:
4979 type: string 5279 type: string
4980 format: date-time 5280 format: date-time
5281 deletedAt:
5282 nullable: true
5283 type: string
5284 format: date-time
5285 default: null
5286 isDeleted:
5287 type: boolean
5288 default: false
4981 totalRepliesFromVideoAuthor: 5289 totalRepliesFromVideoAuthor:
4982 type: integer 5290 type: integer
4983 minimum: 0 5291 minimum: 0
@@ -5035,7 +5343,7 @@ components:
5035 type: string 5343 type: string
5036 format: url 5344 format: url
5037 name: 5345 name:
5038 description: immutable name of the actor 5346 description: immutable name of the actor, used to find or mention it
5039 allOf: 5347 allOf:
5040 - $ref: '#/components/schemas/username' 5348 - $ref: '#/components/schemas/username'
5041 host: 5349 host:
@@ -5071,7 +5379,9 @@ components:
5071 - $ref: '#/components/schemas/User/properties/id' 5379 - $ref: '#/components/schemas/User/properties/id'
5072 displayName: 5380 displayName:
5073 type: string 5381 type: string
5074 description: name displayed on the account's profile 5382 description: editable name of the account, displayed in its representations
5383 minLength: 3
5384 maxLength: 120
5075 description: 5385 description:
5076 type: string 5386 type: string
5077 description: text or bio displayed on the account's profile 5387 description: text or bio displayed on the account's profile
@@ -5079,6 +5389,7 @@ components:
5079 properties: 5389 properties:
5080 currentTime: 5390 currentTime:
5081 type: integer 5391 type: integer
5392 format: seconds
5082 description: timestamp within the video, in seconds 5393 description: timestamp within the video, in seconds
5083 example: 5 5394 example: 5
5084 ServerConfig: 5395 ServerConfig:
@@ -5593,7 +5904,7 @@ components:
5593 type: boolean 5904 type: boolean
5594 support: 5905 support:
5595 description: A text tell the audience how to support the video creator 5906 description: A text tell the audience how to support the video creator
5596 example: Please support my work on <insert crowdfunding plateform>! <3 5907 example: Please support our work on https://soutenir.framasoft.org/en/ <3
5597 type: string 5908 type: string
5598 nsfw: 5909 nsfw:
5599 description: Whether or not this video contains sensitive content 5910 description: Whether or not this video contains sensitive content
@@ -5822,9 +6133,9 @@ components:
5822 UpdateUser: 6133 UpdateUser:
5823 properties: 6134 properties:
5824 email: 6135 email:
5825 type: string
5826 format: email
5827 description: The updated email of the user 6136 description: The updated email of the user
6137 allOf:
6138 - $ref: '#/components/schemas/User/properties/email'
5828 emailVerified: 6139 emailVerified:
5829 type: boolean 6140 type: boolean
5830 description: Set the email as verified 6141 description: Set the email as verified
@@ -5844,28 +6155,54 @@ components:
5844 adminFlags: 6155 adminFlags:
5845 $ref: '#/components/schemas/UserAdminFlags' 6156 $ref: '#/components/schemas/UserAdminFlags'
5846 UpdateMe: 6157 UpdateMe:
6158 # see shared/models/users/user-update-me.model.ts:
5847 properties: 6159 properties:
5848 password: 6160 password:
5849 $ref: '#/components/schemas/password' 6161 $ref: '#/components/schemas/password'
6162 currentPassword:
6163 $ref: '#/components/schemas/password'
5850 email: 6164 email:
6165 description: new email used for login and service communications
6166 allOf:
6167 - $ref: '#/components/schemas/User/properties/email'
6168 displayName:
5851 type: string 6169 type: string
5852 format: email 6170 description: new name of the user in its representations
5853 description: Your new email 6171 minLength: 3
6172 maxLength: 120
5854 displayNSFW: 6173 displayNSFW:
5855 type: string 6174 type: string
5856 description: Your new displayNSFW 6175 description: new NSFW display policy
5857 enum: 6176 enum:
5858 - 'true' 6177 - 'true'
5859 - 'false' 6178 - 'false'
5860 - both 6179 - both
6180 webTorrentEnabled:
6181 type: boolean
6182 description: whether to enable P2P in the player or not
5861 autoPlayVideo: 6183 autoPlayVideo:
5862 type: boolean 6184 type: boolean
5863 description: Your new autoPlayVideo 6185 description: new preference regarding playing videos automatically
5864 required: 6186 autoPlayNextVideo:
5865 - password 6187 type: boolean
5866 - email 6188 description: new preference regarding playing following videos automatically
5867 - displayNSFW 6189 autoPlayNextVideoPlaylist:
5868 - autoPlayVideo 6190 type: boolean
6191 description: new preference regarding playing following playlist videos automatically
6192 videosHistoryEnabled:
6193 type: boolean
6194 description: whether to keep track of watched history or not
6195 videoLanguages:
6196 type: array
6197 items:
6198 type: string
6199 description: list of languages to filter videos down to
6200 theme:
6201 type: string
6202 noInstanceConfigWarningModal:
6203 type: boolean
6204 noWelcomeModal:
6205 type: boolean
5869 GetMeVideoRating: 6206 GetMeVideoRating:
5870 properties: 6207 properties:
5871 id: 6208 id:
@@ -5897,38 +6234,94 @@ components:
5897 RegisterUser: 6234 RegisterUser:
5898 properties: 6235 properties:
5899 username: 6236 username:
5900 $ref: '#/components/schemas/username' 6237 description: immutable name of the user, used to find or mention its actor
6238 allOf:
6239 - $ref: '#/components/schemas/username'
5901 password: 6240 password:
5902 $ref: '#/components/schemas/password' 6241 $ref: '#/components/schemas/password'
5903 email: 6242 email:
5904 type: string 6243 type: string
5905 format: email 6244 format: email
5906 description: The email of the user 6245 description: email of the user, used for login or service communications
5907 displayName: 6246 displayName:
5908 type: string 6247 type: string
5909 description: The user display name 6248 description: editable name of the user, displayed in its representations
5910 minLength: 1 6249 minLength: 1
5911 maxLength: 120 6250 maxLength: 120
5912 channel: 6251 channel:
5913 type: object 6252 type: object
6253 description: channel base information used to create the first channel of the user
5914 properties: 6254 properties:
5915 name: 6255 name:
5916 $ref: '#/components/schemas/usernameChannel' 6256 $ref: '#/components/schemas/usernameChannel'
5917 displayName: 6257 displayName:
5918 type: string 6258 $ref: '#/components/schemas/VideoChannel/properties/displayName'
5919 description: The display name for the default channel
5920 minLength: 1
5921 maxLength: 120
5922 required: 6259 required:
5923 - username 6260 - username
5924 - password 6261 - password
5925 - email 6262 - email
5926 6263
6264 OAuthClient:
6265 properties:
6266 client_id:
6267 type: string
6268 pattern: /^[a-z0-9]$/
6269 maxLength: 32
6270 minLength: 32
6271 example: v1ikx5hnfop4mdpnci8nsqh93c45rldf
6272 client_secret:
6273 type: string
6274 pattern: /^[a-zA-Z0-9]$/
6275 maxLength: 32
6276 minLength: 32
6277 example: AjWiOapPltI6EnsWQwlFarRtLh4u8tDt
6278 OAuthToken-password:
6279 allOf:
6280 - $ref: '#/components/schemas/OAuthClient'
6281 - type: object
6282 properties:
6283 grant_type:
6284 type: string
6285 enum:
6286 - password
6287 - refresh_token
6288 default: password
6289 username:
6290 $ref: '#/components/schemas/User/properties/username'
6291 password:
6292 $ref: '#/components/schemas/password'
6293 required:
6294 - client_id
6295 - client_secret
6296 - grant_type
6297 - username
6298 - password
6299 OAuthToken-refresh_token:
6300 allOf:
6301 - $ref: '#/components/schemas/OAuthClient'
6302 - type: object
6303 properties:
6304 grant_type:
6305 type: string
6306 enum:
6307 - password
6308 - refresh_token
6309 default: password
6310 refresh_token:
6311 type: string
6312 example: 2e0d675df9fc96d2e4ec8a3ebbbf45eca9137bb7
6313 required:
6314 - client_id
6315 - client_secret
6316 - grant_type
6317 - refresh_token
6318
5927 VideoChannel: 6319 VideoChannel:
5928 properties: 6320 properties:
5929 # GET/POST/PUT properties 6321 # GET/POST/PUT properties
5930 displayName: 6322 displayName:
5931 type: string 6323 type: string
6324 description: editable name of the channel, displayed in its representations
5932 example: Videos of Framasoft 6325 example: Videos of Framasoft
5933 minLength: 1 6326 minLength: 1
5934 maxLength: 120 6327 maxLength: 120
@@ -5940,7 +6333,7 @@ components:
5940 support: 6333 support:
5941 type: string 6334 type: string
5942 description: text shown by default on all videos of this channel, to tell the audience how to support it 6335 description: text shown by default on all videos of this channel, to tell the audience how to support it
5943 example: Please support my work on <insert crowdfunding plateform>! <3 6336 example: Please support our work on https://soutenir.framasoft.org/en/ <3
5944 minLength: 3 6337 minLength: 3
5945 maxLength: 1000 6338 maxLength: 1000
5946 # GET-only properties 6339 # GET-only properties