aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--client/src/sass/player/peertube-skin.scss5
-rw-r--r--client/src/standalone/videos/embed.ts2
-rw-r--r--config/default.yaml4
-rw-r--r--config/production.yaml.example12
-rw-r--r--package.json1
-rwxr-xr-xscripts/client-report.sh4
-rw-r--r--server/controllers/activitypub/client.ts39
-rw-r--r--server/controllers/activitypub/inbox.ts5
-rw-r--r--server/controllers/activitypub/outbox.ts10
-rw-r--r--server/controllers/api/search.ts5
-rw-r--r--server/controllers/api/users/index.ts5
-rw-r--r--server/controllers/api/users/me.ts13
-rw-r--r--server/controllers/api/users/my-history.ts1
-rw-r--r--server/controllers/api/video-channel.ts9
-rw-r--r--server/controllers/api/video-playlist.ts36
-rw-r--r--server/controllers/api/videos/abuse.ts20
-rw-r--r--server/controllers/api/videos/blacklist.ts17
-rw-r--r--server/controllers/api/videos/captions.ts9
-rw-r--r--server/controllers/api/videos/comment.ts17
-rw-r--r--server/controllers/api/videos/import.ts38
-rw-r--r--server/controllers/api/videos/index.ts19
-rw-r--r--server/controllers/api/videos/ownership.ts5
-rw-r--r--server/controllers/api/videos/rate.ts2
-rw-r--r--server/controllers/api/videos/watching.ts2
-rw-r--r--server/controllers/feeds.ts2
-rw-r--r--server/controllers/services.ts2
-rw-r--r--server/controllers/static.ts6
-rw-r--r--server/controllers/webfinger.ts2
-rw-r--r--server/helpers/activitypub.ts3
-rw-r--r--server/helpers/actor.ts9
-rw-r--r--server/helpers/captions-utils.ts6
-rw-r--r--server/helpers/custom-jsonld-signature.ts5
-rw-r--r--server/helpers/custom-validators/activitypub/actor.ts15
-rw-r--r--server/helpers/custom-validators/video-ownership.ts22
-rw-r--r--server/helpers/middlewares/accounts.ts7
-rw-r--r--server/helpers/middlewares/video-abuses.ts44
-rw-r--r--server/helpers/middlewares/video-captions.ts4
-rw-r--r--server/helpers/middlewares/video-channels.ts45
-rw-r--r--server/helpers/middlewares/video-playlists.ts35
-rw-r--r--server/helpers/middlewares/videos.ts26
-rw-r--r--server/helpers/peertube-crypto.ts126
-rw-r--r--server/helpers/utils.ts5
-rw-r--r--server/helpers/video.ts40
-rw-r--r--server/helpers/webfinger.ts3
-rw-r--r--server/initializers/constants.ts2
-rw-r--r--server/initializers/migrations/0425-nullable-actor-fields.ts26
-rw-r--r--server/lib/activitypub/actor.ts82
-rw-r--r--server/lib/activitypub/audience.ts27
-rw-r--r--server/lib/activitypub/cache-file.ts14
-rw-r--r--server/lib/activitypub/playlist.ts19
-rw-r--r--server/lib/activitypub/process/process-accept.ts5
-rw-r--r--server/lib/activitypub/process/process-announce.ts7
-rw-r--r--server/lib/activitypub/process/process-create.ts14
-rw-r--r--server/lib/activitypub/process/process-delete.ts26
-rw-r--r--server/lib/activitypub/process/process-dislike.ts4
-rw-r--r--server/lib/activitypub/process/process-flag.ts44
-rw-r--r--server/lib/activitypub/process/process-follow.ts21
-rw-r--r--server/lib/activitypub/process/process-like.ts4
-rw-r--r--server/lib/activitypub/process/process-reject.ts4
-rw-r--r--server/lib/activitypub/process/process-undo.ts12
-rw-r--r--server/lib/activitypub/process/process-update.ts15
-rw-r--r--server/lib/activitypub/process/process-view.ts6
-rw-r--r--server/lib/activitypub/process/process.ts11
-rw-r--r--server/lib/activitypub/send/send-accept.ts7
-rw-r--r--server/lib/activitypub/send/send-announce.ts15
-rw-r--r--server/lib/activitypub/send/send-create.ts34
-rw-r--r--server/lib/activitypub/send/send-delete.ts12
-rw-r--r--server/lib/activitypub/send/send-dislike.ts7
-rw-r--r--server/lib/activitypub/send/send-flag.ts9
-rw-r--r--server/lib/activitypub/send/send-follow.ts6
-rw-r--r--server/lib/activitypub/send/send-like.ts7
-rw-r--r--server/lib/activitypub/send/send-reject.ts7
-rw-r--r--server/lib/activitypub/send/send-undo.ts34
-rw-r--r--server/lib/activitypub/send/send-update.ts31
-rw-r--r--server/lib/activitypub/send/send-view.ts6
-rw-r--r--server/lib/activitypub/send/utils.ts35
-rw-r--r--server/lib/activitypub/share.ts21
-rw-r--r--server/lib/activitypub/url.ts60
-rw-r--r--server/lib/activitypub/video-comments.ts16
-rw-r--r--server/lib/activitypub/video-rates.ts24
-rw-r--r--server/lib/activitypub/videos.ts141
-rw-r--r--server/lib/avatar.ts8
-rw-r--r--server/lib/blocklist.ts5
-rw-r--r--server/lib/client-html.ts11
-rw-r--r--server/lib/emailer.ts38
-rw-r--r--server/lib/hls.ts6
-rw-r--r--server/lib/job-queue/handlers/activitypub-follow.ts20
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-fetcher.ts5
-rw-r--r--server/lib/job-queue/handlers/utils/activitypub-http-utils.ts4
-rw-r--r--server/lib/job-queue/handlers/video-file-import.ts11
-rw-r--r--server/lib/job-queue/handlers/video-import.ts52
-rw-r--r--server/lib/job-queue/handlers/video-transcoding.ts7
-rw-r--r--server/lib/notifier.ts101
-rw-r--r--server/lib/oauth-model.ts3
-rw-r--r--server/lib/peertube-socket.ts7
-rw-r--r--server/lib/redundancy.ts3
-rw-r--r--server/lib/schedulers/videos-redundancy-scheduler.ts57
-rw-r--r--server/lib/thumbnail.ts26
-rw-r--r--server/lib/user.ts33
-rw-r--r--server/lib/video-blacklist.ts13
-rw-r--r--server/lib/video-channel.ts19
-rw-r--r--server/lib/video-comment.ts11
-rw-r--r--server/lib/video-playlist.ts7
-rw-r--r--server/lib/video-transcoding.ts16
-rw-r--r--server/middlewares/activitypub.ts2
-rw-r--r--server/middlewares/validators/follows.ts3
-rw-r--r--server/middlewares/validators/redundancy.ts4
-rw-r--r--server/middlewares/validators/users.ts3
-rw-r--r--server/middlewares/validators/videos/video-abuses.ts4
-rw-r--r--server/middlewares/validators/videos/video-blacklist.ts6
-rw-r--r--server/middlewares/validators/videos/video-captions.ts6
-rw-r--r--server/middlewares/validators/videos/video-channels.ts4
-rw-r--r--server/middlewares/validators/videos/video-comments.ts40
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts86
-rw-r--r--server/middlewares/validators/videos/video-shares.ts2
-rw-r--r--server/middlewares/validators/videos/videos.ts20
-rw-r--r--server/middlewares/validators/webfinger.ts3
-rw-r--r--server/models/account/account-blocklist.ts8
-rw-r--r--server/models/account/account-video-rate.ts28
-rw-r--r--server/models/account/account.ts25
-rw-r--r--server/models/account/user-notification-setting.ts3
-rw-r--r--server/models/account/user-notification.ts10
-rw-r--r--server/models/account/user-video-history.ts7
-rw-r--r--server/models/account/user.ts40
-rw-r--r--server/models/activitypub/actor-follow.ts28
-rw-r--r--server/models/activitypub/actor.ts63
-rw-r--r--server/models/avatar/avatar.ts3
-rw-r--r--server/models/oauth/oauth-token.ts19
-rw-r--r--server/models/redundancy/video-redundancy.ts11
-rw-r--r--server/models/server/plugin.ts12
-rw-r--r--server/models/server/server-blocklist.ts8
-rw-r--r--server/models/server/server.ts7
-rw-r--r--server/models/video/schedule-video-update.ts3
-rw-r--r--server/models/video/tag.ts15
-rw-r--r--server/models/video/video-abuse.ts27
-rw-r--r--server/models/video/video-blacklist.ts8
-rw-r--r--server/models/video/video-caption.ts16
-rw-r--r--server/models/video/video-change-ownership.ts8
-rw-r--r--server/models/video/video-channel.ts67
-rw-r--r--server/models/video/video-comment.ts87
-rw-r--r--server/models/video/video-file.ts3
-rw-r--r--server/models/video/video-format-utils.ts17
-rw-r--r--server/models/video/video-import.ts14
-rw-r--r--server/models/video/video-playlist-element.ts34
-rw-r--r--server/models/video/video-playlist.ts23
-rw-r--r--server/models/video/video-share.ts14
-rw-r--r--server/models/video/video-streaming-playlist.ts12
-rw-r--r--server/models/video/video.ts149
-rw-r--r--server/tests/api/activitypub/helpers.ts13
-rw-r--r--server/tests/api/search/search-videos.ts8
-rw-r--r--server/tests/api/videos/video-abuse.ts77
-rw-r--r--server/tests/api/videos/video-change-ownership.ts2
-rw-r--r--server/tools/cli.ts33
-rw-r--r--server/tools/peertube-import-videos.ts85
-rw-r--r--server/typings/activitypub-processor.model.ts7
-rw-r--r--server/typings/express.ts124
-rw-r--r--server/typings/models/account/account-blocklist.ts25
-rw-r--r--server/typings/models/account/account.ts95
-rw-r--r--server/typings/models/account/actor-follow.ts67
-rw-r--r--server/typings/models/account/actor.ts121
-rw-r--r--server/typings/models/account/avatar.ts11
-rw-r--r--server/typings/models/account/index.d.ts5
-rw-r--r--server/typings/models/actor-follow.ts8
-rw-r--r--server/typings/models/actor.ts22
-rw-r--r--server/typings/models/index.d.ts6
-rw-r--r--server/typings/models/oauth/index.d.ts2
-rw-r--r--server/typings/models/oauth/oauth-client.ts3
-rw-r--r--server/typings/models/oauth/oauth-token.ts13
-rw-r--r--server/typings/models/server/index.d.ts3
-rw-r--r--server/typings/models/server/plugin.ts10
-rw-r--r--server/typings/models/server/server-blocklist.ts23
-rw-r--r--server/typings/models/server/server.ts24
-rw-r--r--server/typings/models/user/index.d.ts4
-rw-r--r--server/typings/models/user/user-notification-setting.ts9
-rw-r--r--server/typings/models/user/user-notification.ts77
-rw-r--r--server/typings/models/user/user-video-history.ts5
-rw-r--r--server/typings/models/user/user.ts70
-rw-r--r--server/typings/models/video-share.ts3
-rw-r--r--server/typings/models/video/index.d.ts18
-rw-r--r--server/typings/models/video/schedule-video-update.ts9
-rw-r--r--server/typings/models/video/tag.ts3
-rw-r--r--server/typings/models/video/thumbnail.ts3
-rw-r--r--server/typings/models/video/video-abuse.ts31
-rw-r--r--server/typings/models/video/video-blacklist.ts24
-rw-r--r--server/typings/models/video/video-caption.ts24
-rw-r--r--server/typings/models/video/video-change-ownership.ts23
-rw-r--r--server/typings/models/video/video-channels.ts126
-rw-r--r--server/typings/models/video/video-comment.ts57
-rw-r--r--server/typings/models/video/video-file.ts19
-rw-r--r--server/typings/models/video/video-import.ts31
-rw-r--r--server/typings/models/video/video-playlist-element.ts34
-rw-r--r--server/typings/models/video/video-playlist.ts92
-rw-r--r--server/typings/models/video/video-rate.ts23
-rw-r--r--server/typings/models/video/video-redundancy.ts38
-rw-r--r--server/typings/models/video/video-share.ts17
-rw-r--r--server/typings/models/video/video-streaming-playlist.ts19
-rw-r--r--server/typings/models/video/video.ts173
-rw-r--r--server/typings/utils.ts12
-rw-r--r--shared/models/activitypub/activity.ts2
-rw-r--r--shared/models/activitypub/objects/video-abuse-object.ts2
-rw-r--r--support/doc/api/openapi.yaml346
-rw-r--r--support/doc/tools.md26
-rw-r--r--tsconfig.json8
-rw-r--r--yarn.lock124
205 files changed, 3601 insertions, 1557 deletions
diff --git a/README.md b/README.md
index 29478b085..4a785478c 100644
--- a/README.md
+++ b/README.md
@@ -182,7 +182,7 @@ See the [architecture blueprint](https://docs.joinpeertube.org/#/contribute-arch
182 182
183See our REST API documentation: 183See our REST API documentation:
184 * OpenAPI 3.0.0 schema: [/support/doc/api/openapi.yaml](/support/doc/api/openapi.yaml) 184 * OpenAPI 3.0.0 schema: [/support/doc/api/openapi.yaml](/support/doc/api/openapi.yaml)
185 * Spec explorer: [docs.joinpeertube.org/#/api-rest-reference.html](https://docs.joinpeertube.org/#/api-rest-reference.html) 185 * Spec explorer: [docs.joinpeertube.org/api-rest-reference.html](https://docs.joinpeertube.org/api-rest-reference.html)
186 186
187See our [ActivityPub documentation](https://docs.joinpeertube.org/#/api-activitypub). 187See our [ActivityPub documentation](https://docs.joinpeertube.org/#/api-activitypub).
188 188
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss
index 1a5144b11..4bf48a570 100644
--- a/client/src/sass/player/peertube-skin.scss
+++ b/client/src/sass/player/peertube-skin.scss
@@ -26,11 +26,6 @@ body {
26 .vjs-dock-description { 26 .vjs-dock-description {
27 font-size: 11px; 27 font-size: 11px;
28 28
29 .text::before, .text::after {
30 display: inline-block;
31 content: '\1F308';
32 }
33
34 .text::before { 29 .text::before {
35 margin-right: 4px; 30 margin-right: 4px;
36 } 31 }
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index 6ff3efef1..19d2a1d02 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -239,7 +239,7 @@ export class PeerTubeEmbed {
239 239
240 const config: ServerConfig = await configResponse.json() 240 const config: ServerConfig = await configResponse.json()
241 const description = config.tracker.enabled && this.warningTitle 241 const description = config.tracker.enabled && this.warningTitle
242 ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>' 242 ? '<span class="text">' + this.player.localize('Watching this video may reveal your IP address to others.') + '</span>'
243 : undefined 243 : undefined
244 244
245 this.player.dock({ 245 this.player.dock({
diff --git a/config/default.yaml b/config/default.yaml
index dfba23f59..b8ba7006a 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -69,7 +69,7 @@ email:
69 69
70# From the project root directory 70# From the project root directory
71storage: 71storage:
72 tmp: 'storage/tmp/' # Used to download data (imports etc), store uploaded files before processing... 72 tmp: 'storage/tmp/' # Use to download data (imports etc), store uploaded files before processing...
73 avatars: 'storage/avatars/' 73 avatars: 'storage/avatars/'
74 videos: 'storage/videos/' 74 videos: 'storage/videos/'
75 streaming_playlists: 'storage/streaming-playlists/' 75 streaming_playlists: 'storage/streaming-playlists/'
@@ -85,7 +85,7 @@ storage:
85log: 85log:
86 level: 'info' # debug/info/warning/error 86 level: 'info' # debug/info/warning/error
87 rotation: 87 rotation:
88 enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate 88 enabled : true
89 89
90search: 90search:
91 # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance 91 # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance
diff --git a/config/production.yaml.example b/config/production.yaml.example
index 267186e08..2dec5ef0c 100644
--- a/config/production.yaml.example
+++ b/config/production.yaml.example
@@ -70,11 +70,11 @@ email:
70 70
71# From the project root directory 71# From the project root directory
72storage: 72storage:
73 tmp: '/var/www/peertube/storage/tmp/' # Used to download data (imports etc), store uploaded files before processing... 73 tmp: '/var/www/peertube/storage/tmp/' # Use to download data (imports etc), store uploaded files before processing...
74 avatars: '/var/www/peertube/storage/avatars/' 74 avatars: '/var/www/peertube/storage/avatars/'
75 videos: '/var/www/peertube/storage/videos/' 75 videos: '/var/www/peertube/storage/videos/'
76 streaming_playlists: '/var/www/peertube/storage/streaming-playlists/' 76 streaming_playlists: '/var/www/peertube/storage/streaming-playlists/'
77 redundancy: '/var/www/peertube/storage/videos/' 77 redundancy: '/var/www/peertube/storage/redundancy/'
78 logs: '/var/www/peertube/storage/logs/' 78 logs: '/var/www/peertube/storage/logs/'
79 previews: '/var/www/peertube/storage/previews/' 79 previews: '/var/www/peertube/storage/previews/'
80 thumbnails: '/var/www/peertube/storage/thumbnails/' 80 thumbnails: '/var/www/peertube/storage/thumbnails/'
@@ -86,7 +86,7 @@ storage:
86log: 86log:
87 level: 'info' # debug/info/warning/error 87 level: 'info' # debug/info/warning/error
88 rotation: 88 rotation:
89 enabled : true 89 enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate
90 90
91search: 91search:
92 # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance 92 # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance
@@ -157,8 +157,8 @@ views:
157 max_age: -1 157 max_age: -1
158 158
159plugins: 159plugins:
160 # The website PeerTube will ask for available PeerTube plugins 160 # The website PeerTube will ask for available PeerTube plugins and themes
161 # This is an unmoderated plugin index, so only install plugins you trust 161 # This is an unmoderated plugin index, so only install plugins/themes you trust
162 index: 162 index:
163 enabled: true 163 enabled: true
164 check_latest_versions_interval: '12 hours' # How often you want to check new plugins/themes versions 164 check_latest_versions_interval: '12 hours' # How often you want to check new plugins/themes versions
@@ -278,7 +278,7 @@ services:
278 username: '@Chocobozzz' # Indicates the Twitter account for the website or platform on which the content was published 278 username: '@Chocobozzz' # Indicates the Twitter account for the website or platform on which the content was published
279 # If true, a video player will be embedded in the Twitter feed on PeerTube video share 279 # If true, a video player will be embedded in the Twitter feed on PeerTube video share
280 # If false, we use an image link card that will redirect on your PeerTube instance 280 # If false, we use an image link card that will redirect on your PeerTube instance
281 # Test on https://cards-dev.twitter.com/validator to see if you are whitelisted 281 # Change it to "true", and then test on https://cards-dev.twitter.com/validator to see if you are whitelisted
282 whitelisted: false 282 whitelisted: false
283 283
284followers: 284followers:
diff --git a/package.json b/package.json
index ce689a4b3..0c1ec93d1 100644
--- a/package.json
+++ b/package.json
@@ -128,7 +128,6 @@
128 "iso-639-3": "^1.0.1", 128 "iso-639-3": "^1.0.1",
129 "js-yaml": "^3.5.4", 129 "js-yaml": "^3.5.4",
130 "jsonld": "~1.1.0", 130 "jsonld": "~1.1.0",
131 "jsonld-signatures": "https://github.com/Chocobozzz/jsonld-signatures#rsa2017",
132 "lodash": "^4.17.10", 131 "lodash": "^4.17.10",
133 "lru-cache": "^5.1.1", 132 "lru-cache": "^5.1.1",
134 "magnet-uri": "^5.1.4", 133 "magnet-uri": "^5.1.4",
diff --git a/scripts/client-report.sh b/scripts/client-report.sh
index df7ccda27..a758a211c 100755
--- a/scripts/client-report.sh
+++ b/scripts/client-report.sh
@@ -5,5 +5,5 @@ set -eu
5gawk -i inplace 'BEGIN { found=0 } { if (found || $0 ~ /^{/) { found=1; print }}' ./client/dist/embed-stats.json 5gawk -i inplace 'BEGIN { found=0 } { if (found || $0 ~ /^{/) { found=1; print }}' ./client/dist/embed-stats.json
6 6
7npm run concurrently -- -k \ 7npm run concurrently -- -k \
8 "cd client && npm run webpack-bundle-analyzer -- -p 8888 ./dist/en_US/stats.json" \ 8 "cd client && npm run webpack-bundle-analyzer -- -p 8888 ./dist/en_US/stats-es2015.json" \
9 "cd client && npm run webpack-bundle-analyzer -- -p 8889 ./dist/embed-stats.json" \ No newline at end of file 9 "cd client && npm run webpack-bundle-analyzer -- -p 8889 ./dist/embed-stats.json"
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index 11504b354..453ced8bf 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -16,7 +16,6 @@ import {
16} from '../../middlewares' 16} from '../../middlewares'
17import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' 17import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators'
18import { AccountModel } from '../../models/account/account' 18import { AccountModel } from '../../models/account/account'
19import { ActorModel } from '../../models/activitypub/actor'
20import { ActorFollowModel } from '../../models/activitypub/actor-follow' 19import { ActorFollowModel } from '../../models/activitypub/actor-follow'
21import { VideoModel } from '../../models/video/video' 20import { VideoModel } from '../../models/video/video'
22import { VideoCommentModel } from '../../models/video/video-comment' 21import { VideoCommentModel } from '../../models/video/video-comment'
@@ -38,6 +37,7 @@ import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike'
38import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' 37import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists'
39import { VideoPlaylistModel } from '../../models/video/video-playlist' 38import { VideoPlaylistModel } from '../../models/video/video-playlist'
40import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' 39import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
40import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption } from '@server/typings/models'
41 41
42const activityPubClientRouter = express.Router() 42const activityPubClientRouter = express.Router()
43 43
@@ -148,7 +148,7 @@ activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistT
148 148
149activityPubClientRouter.get('/video-playlists/:playlistId', 149activityPubClientRouter.get('/video-playlists/:playlistId',
150 executeIfActivityPub, 150 executeIfActivityPub,
151 asyncMiddleware(videoPlaylistsGetValidator), 151 asyncMiddleware(videoPlaylistsGetValidator('all')),
152 asyncMiddleware(videoPlaylistController) 152 asyncMiddleware(videoPlaylistController)
153) 153)
154activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', 154activityPubClientRouter.get('/video-playlists/:playlistId/:videoId',
@@ -208,18 +208,19 @@ function getAccountVideoRate (rateType: VideoRateType) {
208 208
209async function videoController (req: express.Request, res: express.Response) { 209async function videoController (req: express.Request, res: express.Response) {
210 // We need more attributes 210 // We need more attributes
211 const video = await VideoModel.loadForGetAPI({ id: res.locals.video.id }) 211 const video = await VideoModel.loadForGetAPI({ id: res.locals.onlyVideoWithRights.id }) as MVideoAPWithoutCaption
212 212
213 if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url) 213 if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url)
214 214
215 // We need captions to render AP object 215 // We need captions to render AP object
216 video.VideoCaptions = await VideoCaptionModel.listVideoCaptions(video.id) 216 const captions = await VideoCaptionModel.listVideoCaptions(video.id)
217 const videoWithCaptions = Object.assign(video, { VideoCaptions: captions })
217 218
218 const audience = getAudience(video.VideoChannel.Account.Actor, video.privacy === VideoPrivacy.PUBLIC) 219 const audience = getAudience(videoWithCaptions.VideoChannel.Account.Actor, videoWithCaptions.privacy === VideoPrivacy.PUBLIC)
219 const videoObject = audiencify(video.toActivityPubObject(), audience) 220 const videoObject = audiencify(videoWithCaptions.toActivityPubObject(), audience)
220 221
221 if (req.path.endsWith('/activity')) { 222 if (req.path.endsWith('/activity')) {
222 const data = buildCreateActivity(video.url, video.VideoChannel.Account.Actor, videoObject, audience) 223 const data = buildCreateActivity(videoWithCaptions.url, video.VideoChannel.Account.Actor, videoObject, audience)
223 return activityPubResponse(activityPubContextify(data), res) 224 return activityPubResponse(activityPubContextify(data), res)
224 } 225 }
225 226
@@ -231,13 +232,13 @@ async function videoAnnounceController (req: express.Request, res: express.Respo
231 232
232 if (share.url.startsWith(WEBSERVER.URL) === false) return res.redirect(share.url) 233 if (share.url.startsWith(WEBSERVER.URL) === false) return res.redirect(share.url)
233 234
234 const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.video, undefined) 235 const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined)
235 236
236 return activityPubResponse(activityPubContextify(activity), res) 237 return activityPubResponse(activityPubContextify(activity), res)
237} 238}
238 239
239async function videoAnnouncesController (req: express.Request, res: express.Response) { 240async function videoAnnouncesController (req: express.Request, res: express.Response) {
240 const video = res.locals.video 241 const video = res.locals.onlyVideo
241 242
242 const handler = async (start: number, count: number) => { 243 const handler = async (start: number, count: number) => {
243 const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) 244 const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count)
@@ -252,21 +253,21 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp
252} 253}
253 254
254async function videoLikesController (req: express.Request, res: express.Response) { 255async function videoLikesController (req: express.Request, res: express.Response) {
255 const video = res.locals.video 256 const video = res.locals.onlyVideo
256 const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) 257 const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video))
257 258
258 return activityPubResponse(activityPubContextify(json), res) 259 return activityPubResponse(activityPubContextify(json), res)
259} 260}
260 261
261async function videoDislikesController (req: express.Request, res: express.Response) { 262async function videoDislikesController (req: express.Request, res: express.Response) {
262 const video = res.locals.video 263 const video = res.locals.onlyVideo
263 const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) 264 const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video))
264 265
265 return activityPubResponse(activityPubContextify(json), res) 266 return activityPubResponse(activityPubContextify(json), res)
266} 267}
267 268
268async function videoCommentsController (req: express.Request, res: express.Response) { 269async function videoCommentsController (req: express.Request, res: express.Response) {
269 const video = res.locals.video 270 const video = res.locals.onlyVideo
270 271
271 const handler = async (start: number, count: number) => { 272 const handler = async (start: number, count: number) => {
272 const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) 273 const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count)
@@ -301,7 +302,7 @@ async function videoChannelFollowingController (req: express.Request, res: expre
301} 302}
302 303
303async function videoCommentController (req: express.Request, res: express.Response) { 304async function videoCommentController (req: express.Request, res: express.Response) {
304 const videoComment = res.locals.videoComment 305 const videoComment = res.locals.videoCommentFull
305 306
306 if (videoComment.url.startsWith(WEBSERVER.URL) === false) return res.redirect(videoComment.url) 307 if (videoComment.url.startsWith(WEBSERVER.URL) === false) return res.redirect(videoComment.url)
307 308
@@ -337,7 +338,7 @@ async function videoRedundancyController (req: express.Request, res: express.Res
337} 338}
338 339
339async function videoPlaylistController (req: express.Request, res: express.Response) { 340async function videoPlaylistController (req: express.Request, res: express.Response) {
340 const playlist = res.locals.videoPlaylist 341 const playlist = res.locals.videoPlaylistFull
341 342
342 // We need more attributes 343 // We need more attributes
343 playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId) 344 playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId)
@@ -350,7 +351,7 @@ async function videoPlaylistController (req: express.Request, res: express.Respo
350} 351}
351 352
352async function videoPlaylistElementController (req: express.Request, res: express.Response) { 353async function videoPlaylistElementController (req: express.Request, res: express.Response) {
353 const videoPlaylistElement = res.locals.videoPlaylistElement 354 const videoPlaylistElement = res.locals.videoPlaylistElementAP
354 355
355 const json = videoPlaylistElement.toActivityPubObject() 356 const json = videoPlaylistElement.toActivityPubObject()
356 return activityPubResponse(activityPubContextify(json), res) 357 return activityPubResponse(activityPubContextify(json), res)
@@ -358,7 +359,7 @@ async function videoPlaylistElementController (req: express.Request, res: expres
358 359
359// --------------------------------------------------------------------------- 360// ---------------------------------------------------------------------------
360 361
361async function actorFollowing (req: express.Request, actor: ActorModel) { 362async function actorFollowing (req: express.Request, actor: MActorId) {
362 const handler = (start: number, count: number) => { 363 const handler = (start: number, count: number) => {
363 return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count) 364 return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count)
364 } 365 }
@@ -366,7 +367,7 @@ async function actorFollowing (req: express.Request, actor: ActorModel) {
366 return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) 367 return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
367} 368}
368 369
369async function actorFollowers (req: express.Request, actor: ActorModel) { 370async function actorFollowers (req: express.Request, actor: MActorId) {
370 const handler = (start: number, count: number) => { 371 const handler = (start: number, count: number) => {
371 return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count) 372 return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count)
372 } 373 }
@@ -374,7 +375,7 @@ async function actorFollowers (req: express.Request, actor: ActorModel) {
374 return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) 375 return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
375} 376}
376 377
377async function actorPlaylists (req: express.Request, account: AccountModel) { 378async function actorPlaylists (req: express.Request, account: MAccountId) {
378 const handler = (start: number, count: number) => { 379 const handler = (start: number, count: number) => {
379 return VideoPlaylistModel.listPublicUrlsOfForAP(account.id, start, count) 380 return VideoPlaylistModel.listPublicUrlsOfForAP(account.id, start, count)
380 } 381 }
@@ -382,7 +383,7 @@ async function actorPlaylists (req: express.Request, account: AccountModel) {
382 return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) 383 return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
383} 384}
384 385
385function videoRates (req: express.Request, rateType: VideoRateType, video: VideoModel, url: string) { 386function videoRates (req: express.Request, rateType: VideoRateType, video: MVideo, url: string) {
386 const handler = async (start: number, count: number) => { 387 const handler = async (start: number, count: number) => {
387 const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) 388 const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count)
388 return { 389 return {
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts
index 2d3eef222..d9df253aa 100644
--- a/server/controllers/activitypub/inbox.ts
+++ b/server/controllers/activitypub/inbox.ts
@@ -7,7 +7,7 @@ import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChann
7import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' 7import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
8import { queue } from 'async' 8import { queue } from 'async'
9import { ActorModel } from '../../models/activitypub/actor' 9import { ActorModel } from '../../models/activitypub/actor'
10import { SignatureActorModel } from '../../typings/models' 10import { MActorDefault, MActorSignature } from '../../typings/models'
11 11
12const inboxRouter = express.Router() 12const inboxRouter = express.Router()
13 13
@@ -41,7 +41,8 @@ export {
41 41
42// --------------------------------------------------------------------------- 42// ---------------------------------------------------------------------------
43 43
44const inboxQueue = queue<{ activities: Activity[], signatureActor?: SignatureActorModel, inboxActor?: ActorModel }, Error>((task, cb) => { 44type QueueParam = { activities: Activity[], signatureActor?: MActorSignature, inboxActor?: MActorDefault }
45const inboxQueue = queue<QueueParam, Error>((task, cb) => {
45 const options = { signatureActor: task.signatureActor, inboxActor: task.inboxActor } 46 const options = { signatureActor: task.signatureActor, inboxActor: task.inboxActor }
46 47
47 processActivities(task.activities, options) 48 processActivities(task.activities, options)
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts
index 38b6ec976..f3dd2ad7d 100644
--- a/server/controllers/activitypub/outbox.ts
+++ b/server/controllers/activitypub/outbox.ts
@@ -6,11 +6,9 @@ import { logger } from '../../helpers/logger'
6import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send' 6import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send'
7import { buildAudience } from '../../lib/activitypub/audience' 7import { buildAudience } from '../../lib/activitypub/audience'
8import { asyncMiddleware, localAccountValidator, localVideoChannelValidator } from '../../middlewares' 8import { asyncMiddleware, localAccountValidator, localVideoChannelValidator } from '../../middlewares'
9import { AccountModel } from '../../models/account/account'
10import { ActorModel } from '../../models/activitypub/actor'
11import { VideoModel } from '../../models/video/video' 9import { VideoModel } from '../../models/video/video'
12import { activityPubResponse } from './utils' 10import { activityPubResponse } from './utils'
13import { VideoChannelModel } from '../../models/video/video-channel' 11import { MActorLight } from '@server/typings/models'
14 12
15const outboxRouter = express.Router() 13const outboxRouter = express.Router()
16 14
@@ -45,14 +43,10 @@ async function outboxController (req: express.Request, res: express.Response) {
45 return activityPubResponse(activityPubContextify(json), res) 43 return activityPubResponse(activityPubContextify(json), res)
46} 44}
47 45
48async function buildActivities (actor: ActorModel, start: number, count: number) { 46async function buildActivities (actor: MActorLight, start: number, count: number) {
49 const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count) 47 const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count)
50 const activities: Activity[] = [] 48 const activities: Activity[] = []
51 49
52 // Avoid too many SQL requests
53 const actors = data.data.map(v => v.VideoChannel.Account.Actor)
54 actors.push(actor)
55
56 for (const video of data.data) { 50 for (const video of data.data) {
57 const byActor = video.VideoChannel.Account.Actor 51 const byActor = video.VideoChannel.Account.Actor
58 const createActivityAudience = buildAudience([ byActor.followersUrl ], video.privacy === VideoPrivacy.PUBLIC) 52 const createActivityAudience = buildAudience([ byActor.followersUrl ], video.privacy === VideoPrivacy.PUBLIC)
diff --git a/server/controllers/api/search.ts b/server/controllers/api/search.ts
index 9a1e30b83..349650aca 100644
--- a/server/controllers/api/search.ts
+++ b/server/controllers/api/search.ts
@@ -19,6 +19,7 @@ import { getOrCreateActorAndServerAndModel, getOrCreateVideoAndAccountAndChannel
19import { logger } from '../../helpers/logger' 19import { logger } from '../../helpers/logger'
20import { VideoChannelModel } from '../../models/video/video-channel' 20import { VideoChannelModel } from '../../models/video/video-channel'
21import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' 21import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
22import { MChannelAccountDefault, MVideoAccountLightBlacklistAllFiles } from '../../typings/models'
22 23
23const searchRouter = express.Router() 24const searchRouter = express.Router()
24 25
@@ -84,7 +85,7 @@ async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: expr
84} 85}
85 86
86async function searchVideoChannelURI (search: string, isWebfingerSearch: boolean, res: express.Response) { 87async function searchVideoChannelURI (search: string, isWebfingerSearch: boolean, res: express.Response) {
87 let videoChannel: VideoChannelModel 88 let videoChannel: MChannelAccountDefault
88 let uri = search 89 let uri = search
89 90
90 if (isWebfingerSearch) { 91 if (isWebfingerSearch) {
@@ -137,7 +138,7 @@ async function searchVideosDB (query: VideosSearchQuery, res: express.Response)
137} 138}
138 139
139async function searchVideoURI (url: string, res: express.Response) { 140async function searchVideoURI (url: string, res: express.Response) {
140 let video: VideoModel 141 let video: MVideoAccountLightBlacklistAllFiles
141 142
142 // Check if we can fetch a remote video with the URL 143 // Check if we can fetch a remote video with the URL
143 if (isUserAbleToSearchRemoteURI(res)) { 144 if (isUserAbleToSearchRemoteURI(res)) {
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index ae40e86f8..27351c1a9 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -48,6 +48,7 @@ import { CONFIG } from '../../../initializers/config'
48import { sequelizeTypescript } from '../../../initializers/database' 48import { sequelizeTypescript } from '../../../initializers/database'
49import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' 49import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
50import { UserRegister } from '../../../../shared/models/users/user-register.model' 50import { UserRegister } from '../../../../shared/models/users/user-register.model'
51import { MUser, MUserAccountDefault } from '@server/typings/models'
51 52
52const auditLogger = auditLoggerFactory('users') 53const auditLogger = auditLoggerFactory('users')
53 54
@@ -195,7 +196,7 @@ async function createUser (req: express.Request, res: express.Response) {
195 videoQuota: body.videoQuota, 196 videoQuota: body.videoQuota,
196 videoQuotaDaily: body.videoQuotaDaily, 197 videoQuotaDaily: body.videoQuotaDaily,
197 adminFlags: body.adminFlags || UserAdminFlag.NONE 198 adminFlags: body.adminFlags || UserAdminFlag.NONE
198 }) 199 }) as MUser
199 200
200 const { user, account } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate }) 201 const { user, account } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate })
201 202
@@ -359,7 +360,7 @@ function success (req: express.Request, res: express.Response) {
359 res.end() 360 res.end()
360} 361}
361 362
362async function changeUserBlock (res: express.Response, user: UserModel, block: boolean, reason?: string) { 363async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) {
363 const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) 364 const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
364 365
365 user.blocked = block 366 user.blocked = block
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index e7ed3de64..78e1e7fa3 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -23,15 +23,12 @@ import { createReqFiles } from '../../../helpers/express-utils'
23import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model' 23import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model'
24import { updateAvatarValidator } from '../../../middlewares/validators/avatar' 24import { updateAvatarValidator } from '../../../middlewares/validators/avatar'
25import { updateActorAvatarFile } from '../../../lib/avatar' 25import { updateActorAvatarFile } from '../../../lib/avatar'
26import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
27import { VideoImportModel } from '../../../models/video/video-import' 26import { VideoImportModel } from '../../../models/video/video-import'
28import { AccountModel } from '../../../models/account/account' 27import { AccountModel } from '../../../models/account/account'
29import { CONFIG } from '../../../initializers/config' 28import { CONFIG } from '../../../initializers/config'
30import { sequelizeTypescript } from '../../../initializers/database' 29import { sequelizeTypescript } from '../../../initializers/database'
31import { sendVerifyUserEmail } from '../../../lib/user' 30import { sendVerifyUserEmail } from '../../../lib/user'
32 31
33const auditLogger = auditLoggerFactory('users-me')
34
35const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) 32const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
36 33
37const meRouter = express.Router() 34const meRouter = express.Router()
@@ -147,7 +144,7 @@ async function getUserVideoQuotaUsed (req: express.Request, res: express.Respons
147} 144}
148 145
149async function getUserVideoRating (req: express.Request, res: express.Response) { 146async function getUserVideoRating (req: express.Request, res: express.Response) {
150 const videoId = res.locals.video.id 147 const videoId = res.locals.videoId.id
151 const accountId = +res.locals.oauth.token.User.Account.id 148 const accountId = +res.locals.oauth.token.User.Account.id
152 149
153 const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null) 150 const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null)
@@ -165,8 +162,6 @@ async function deleteMe (req: express.Request, res: express.Response) {
165 162
166 await user.destroy() 163 await user.destroy()
167 164
168 auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})))
169
170 return res.sendStatus(204) 165 return res.sendStatus(204)
171} 166}
172 167
@@ -175,7 +170,6 @@ async function updateMe (req: express.Request, res: express.Response) {
175 let sendVerificationEmail = false 170 let sendVerificationEmail = false
176 171
177 const user = res.locals.oauth.token.user 172 const user = res.locals.oauth.token.user
178 const oldUserAuditView = new UserAuditView(user.toFormattedJSON({}))
179 173
180 if (body.password !== undefined) user.password = body.password 174 if (body.password !== undefined) user.password = body.password
181 if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy 175 if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy
@@ -204,8 +198,6 @@ async function updateMe (req: express.Request, res: express.Response) {
204 await userAccount.save({ transaction: t }) 198 await userAccount.save({ transaction: t })
205 199
206 await sendUpdateActor(userAccount, t) 200 await sendUpdateActor(userAccount, t)
207
208 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView)
209 }) 201 })
210 202
211 if (sendVerificationEmail === true) { 203 if (sendVerificationEmail === true) {
@@ -218,13 +210,10 @@ async function updateMe (req: express.Request, res: express.Response) {
218async function updateMyAvatar (req: express.Request, res: express.Response) { 210async function updateMyAvatar (req: express.Request, res: express.Response) {
219 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] 211 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
220 const user = res.locals.oauth.token.user 212 const user = res.locals.oauth.token.user
221 const oldUserAuditView = new UserAuditView(user.toFormattedJSON({}))
222 213
223 const userAccount = await AccountModel.load(user.Account.id) 214 const userAccount = await AccountModel.load(user.Account.id)
224 215
225 const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount) 216 const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount)
226 217
227 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView)
228
229 return res.json({ avatar: avatar.toFormattedJSON() }) 218 return res.json({ avatar: avatar.toFormattedJSON() })
230} 219}
diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts
index 7025c0ff1..4da1f3496 100644
--- a/server/controllers/api/users/my-history.ts
+++ b/server/controllers/api/users/my-history.ts
@@ -7,7 +7,6 @@ import {
7 setDefaultPagination, 7 setDefaultPagination,
8 userHistoryRemoveValidator 8 userHistoryRemoveValidator
9} from '../../../middlewares' 9} from '../../../middlewares'
10import { UserModel } from '../../../models/account/user'
11import { getFormattedObjects } from '../../../helpers/utils' 10import { getFormattedObjects } from '../../../helpers/utils'
12import { UserVideoHistoryModel } from '../../../models/account/user-video-history' 11import { UserVideoHistoryModel } from '../../../models/account/user-video-history'
13import { sequelizeTypescript } from '../../../initializers' 12import { sequelizeTypescript } from '../../../initializers'
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 81a03a62b..acc5b2987 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -19,7 +19,7 @@ import { VideoChannelModel } from '../../models/video/video-channel'
19import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' 19import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators'
20import { sendUpdateActor } from '../../lib/activitypub/send' 20import { sendUpdateActor } from '../../lib/activitypub/send'
21import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' 21import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
22import { createVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' 22import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
23import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 23import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
24import { setAsyncActorKeys } from '../../lib/activitypub' 24import { setAsyncActorKeys } from '../../lib/activitypub'
25import { AccountModel } from '../../models/account/account' 25import { AccountModel } from '../../models/account/account'
@@ -35,6 +35,7 @@ import { VideoPlaylistModel } from '../../models/video/video-playlist'
35import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' 35import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
36import { CONFIG } from '../../initializers/config' 36import { CONFIG } from '../../initializers/config'
37import { sequelizeTypescript } from '../../initializers/database' 37import { sequelizeTypescript } from '../../initializers/database'
38import { MChannelAccountDefault } from '@server/typings/models'
38 39
39const auditLogger = auditLoggerFactory('channels') 40const auditLogger = auditLoggerFactory('channels')
40const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) 41const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
@@ -136,10 +137,10 @@ async function updateVideoChannelAvatar (req: express.Request, res: express.Resp
136async function addVideoChannel (req: express.Request, res: express.Response) { 137async function addVideoChannel (req: express.Request, res: express.Response) {
137 const videoChannelInfo: VideoChannelCreate = req.body 138 const videoChannelInfo: VideoChannelCreate = req.body
138 139
139 const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => { 140 const videoChannelCreated = await sequelizeTypescript.transaction(async t => {
140 const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) 141 const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
141 142
142 return createVideoChannel(videoChannelInfo, account, t) 143 return createLocalVideoChannel(videoChannelInfo, account, t)
143 }) 144 })
144 145
145 setAsyncActorKeys(videoChannelCreated.Actor) 146 setAsyncActorKeys(videoChannelCreated.Actor)
@@ -181,7 +182,7 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
181 } 182 }
182 } 183 }
183 184
184 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) 185 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) as MChannelAccountDefault
185 await sendUpdateActor(videoChannelInstanceUpdated, t) 186 await sendUpdateActor(videoChannelInstanceUpdated, t)
186 187
187 auditLogger.update( 188 auditLogger.update(
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts
index bd454f553..d9f0ff925 100644
--- a/server/controllers/api/video-playlist.ts
+++ b/server/controllers/api/video-playlist.ts
@@ -40,7 +40,7 @@ import { JobQueue } from '../../lib/job-queue'
40import { CONFIG } from '../../initializers/config' 40import { CONFIG } from '../../initializers/config'
41import { sequelizeTypescript } from '../../initializers/database' 41import { sequelizeTypescript } from '../../initializers/database'
42import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail' 42import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail'
43import { VideoModel } from '../../models/video/video' 43import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/typings/models'
44 44
45const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) 45const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR })
46 46
@@ -58,7 +58,7 @@ videoPlaylistRouter.get('/',
58) 58)
59 59
60videoPlaylistRouter.get('/:playlistId', 60videoPlaylistRouter.get('/:playlistId',
61 asyncMiddleware(videoPlaylistsGetValidator), 61 asyncMiddleware(videoPlaylistsGetValidator('summary')),
62 getVideoPlaylist 62 getVideoPlaylist
63) 63)
64 64
@@ -83,7 +83,7 @@ videoPlaylistRouter.delete('/:playlistId',
83) 83)
84 84
85videoPlaylistRouter.get('/:playlistId/videos', 85videoPlaylistRouter.get('/:playlistId/videos',
86 asyncMiddleware(videoPlaylistsGetValidator), 86 asyncMiddleware(videoPlaylistsGetValidator('summary')),
87 paginationValidator, 87 paginationValidator,
88 setDefaultPagination, 88 setDefaultPagination,
89 optionalAuthenticate, 89 optionalAuthenticate,
@@ -140,7 +140,7 @@ async function listVideoPlaylists (req: express.Request, res: express.Response)
140} 140}
141 141
142function getVideoPlaylist (req: express.Request, res: express.Response) { 142function getVideoPlaylist (req: express.Request, res: express.Response) {
143 const videoPlaylist = res.locals.videoPlaylist 143 const videoPlaylist = res.locals.videoPlaylistSummary
144 144
145 if (videoPlaylist.isOutdated()) { 145 if (videoPlaylist.isOutdated()) {
146 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } }) 146 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } })
@@ -159,7 +159,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
159 description: videoPlaylistInfo.description, 159 description: videoPlaylistInfo.description,
160 privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE, 160 privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE,
161 ownerAccountId: user.Account.id 161 ownerAccountId: user.Account.id
162 }) 162 }) as MVideoPlaylistFull
163 163
164 videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object 164 videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object
165 165
@@ -175,8 +175,8 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
175 ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist, false) 175 ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist, false)
176 : undefined 176 : undefined
177 177
178 const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => { 178 const videoPlaylistCreated = await sequelizeTypescript.transaction(async t => {
179 const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) 179 const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) as MVideoPlaylistFull
180 180
181 if (thumbnailModel) { 181 if (thumbnailModel) {
182 thumbnailModel.automaticallyGenerated = false 182 thumbnailModel.automaticallyGenerated = false
@@ -201,7 +201,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
201} 201}
202 202
203async function updateVideoPlaylist (req: express.Request, res: express.Response) { 203async function updateVideoPlaylist (req: express.Request, res: express.Response) {
204 const videoPlaylistInstance = res.locals.videoPlaylist 204 const videoPlaylistInstance = res.locals.videoPlaylistFull
205 const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON() 205 const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON()
206 const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate 206 const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate
207 207
@@ -275,7 +275,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
275} 275}
276 276
277async function removeVideoPlaylist (req: express.Request, res: express.Response) { 277async function removeVideoPlaylist (req: express.Request, res: express.Response) {
278 const videoPlaylistInstance = res.locals.videoPlaylist 278 const videoPlaylistInstance = res.locals.videoPlaylistSummary
279 279
280 await sequelizeTypescript.transaction(async t => { 280 await sequelizeTypescript.transaction(async t => {
281 await videoPlaylistInstance.destroy({ transaction: t }) 281 await videoPlaylistInstance.destroy({ transaction: t })
@@ -290,10 +290,10 @@ async function removeVideoPlaylist (req: express.Request, res: express.Response)
290 290
291async function addVideoInPlaylist (req: express.Request, res: express.Response) { 291async function addVideoInPlaylist (req: express.Request, res: express.Response) {
292 const body: VideoPlaylistElementCreate = req.body 292 const body: VideoPlaylistElementCreate = req.body
293 const videoPlaylist = res.locals.videoPlaylist 293 const videoPlaylist = res.locals.videoPlaylistFull
294 const video = res.locals.video 294 const video = res.locals.onlyVideo
295 295
296 const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { 296 const playlistElement = await sequelizeTypescript.transaction(async t => {
297 const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t) 297 const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t)
298 298
299 const playlistElement = await VideoPlaylistElementModel.create({ 299 const playlistElement = await VideoPlaylistElementModel.create({
@@ -330,7 +330,7 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response)
330 330
331async function updateVideoPlaylistElement (req: express.Request, res: express.Response) { 331async function updateVideoPlaylistElement (req: express.Request, res: express.Response) {
332 const body: VideoPlaylistElementUpdate = req.body 332 const body: VideoPlaylistElementUpdate = req.body
333 const videoPlaylist = res.locals.videoPlaylist 333 const videoPlaylist = res.locals.videoPlaylistFull
334 const videoPlaylistElement = res.locals.videoPlaylistElement 334 const videoPlaylistElement = res.locals.videoPlaylistElement
335 335
336 const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { 336 const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => {
@@ -354,7 +354,7 @@ async function updateVideoPlaylistElement (req: express.Request, res: express.Re
354 354
355async function removeVideoFromPlaylist (req: express.Request, res: express.Response) { 355async function removeVideoFromPlaylist (req: express.Request, res: express.Response) {
356 const videoPlaylistElement = res.locals.videoPlaylistElement 356 const videoPlaylistElement = res.locals.videoPlaylistElement
357 const videoPlaylist = res.locals.videoPlaylist 357 const videoPlaylist = res.locals.videoPlaylistFull
358 const positionToDelete = videoPlaylistElement.position 358 const positionToDelete = videoPlaylistElement.position
359 359
360 await sequelizeTypescript.transaction(async t => { 360 await sequelizeTypescript.transaction(async t => {
@@ -381,7 +381,7 @@ async function removeVideoFromPlaylist (req: express.Request, res: express.Respo
381} 381}
382 382
383async function reorderVideosPlaylist (req: express.Request, res: express.Response) { 383async function reorderVideosPlaylist (req: express.Request, res: express.Response) {
384 const videoPlaylist = res.locals.videoPlaylist 384 const videoPlaylist = res.locals.videoPlaylistFull
385 const body: VideoPlaylistReorder = req.body 385 const body: VideoPlaylistReorder = req.body
386 386
387 const start: number = body.startPosition 387 const start: number = body.startPosition
@@ -434,7 +434,7 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons
434} 434}
435 435
436async function getVideoPlaylistVideos (req: express.Request, res: express.Response) { 436async function getVideoPlaylistVideos (req: express.Request, res: express.Response) {
437 const videoPlaylistInstance = res.locals.videoPlaylist 437 const videoPlaylistInstance = res.locals.videoPlaylistSummary
438 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined 438 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
439 const server = await getServerActor() 439 const server = await getServerActor()
440 440
@@ -453,7 +453,7 @@ async function getVideoPlaylistVideos (req: express.Request, res: express.Respon
453 return res.json(getFormattedObjects(resultList.data, resultList.total, options)) 453 return res.json(getFormattedObjects(resultList.data, resultList.total, options))
454} 454}
455 455
456async function regeneratePlaylistThumbnail (videoPlaylist: VideoPlaylistModel) { 456async function regeneratePlaylistThumbnail (videoPlaylist: MVideoPlaylistThumbnail) {
457 await videoPlaylist.Thumbnail.destroy() 457 await videoPlaylist.Thumbnail.destroy()
458 videoPlaylist.Thumbnail = null 458 videoPlaylist.Thumbnail = null
459 459
@@ -461,7 +461,7 @@ async function regeneratePlaylistThumbnail (videoPlaylist: VideoPlaylistModel) {
461 if (firstElement) await generateThumbnailForPlaylist(videoPlaylist, firstElement.Video) 461 if (firstElement) await generateThumbnailForPlaylist(videoPlaylist, firstElement.Video)
462} 462}
463 463
464async function generateThumbnailForPlaylist (videoPlaylist: VideoPlaylistModel, video: VideoModel) { 464async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbnail, video: MVideoThumbnail) {
465 logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) 465 logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url)
466 466
467 const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename) 467 const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename)
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts
index 77808466c..4ae899b7e 100644
--- a/server/controllers/api/videos/abuse.ts
+++ b/server/controllers/api/videos/abuse.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared' 2import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { getFormattedObjects } from '../../../helpers/utils' 4import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
5import { sequelizeTypescript } from '../../../initializers' 5import { sequelizeTypescript } from '../../../initializers'
6import { 6import {
7 asyncMiddleware, 7 asyncMiddleware,
@@ -21,6 +21,7 @@ import { VideoAbuseModel } from '../../../models/video/video-abuse'
21import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' 21import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger'
22import { Notifier } from '../../../lib/notifier' 22import { Notifier } from '../../../lib/notifier'
23import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' 23import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag'
24import { MVideoAbuseAccountVideo } from '../../../typings/models/video'
24 25
25const auditLogger = auditLoggerFactory('abuse') 26const auditLogger = auditLoggerFactory('abuse')
26const abuseVideoRouter = express.Router() 27const abuseVideoRouter = express.Router()
@@ -61,7 +62,16 @@ export {
61// --------------------------------------------------------------------------- 62// ---------------------------------------------------------------------------
62 63
63async function listVideoAbuses (req: express.Request, res: express.Response) { 64async function listVideoAbuses (req: express.Request, res: express.Response) {
64 const resultList = await VideoAbuseModel.listForApi(req.query.start, req.query.count, req.query.sort) 65 const user = res.locals.oauth.token.user
66 const serverActor = await getServerActor()
67
68 const resultList = await VideoAbuseModel.listForApi({
69 start: req.query.start,
70 count: req.query.count,
71 sort: req.query.sort,
72 serverAccountId: serverActor.Account.id,
73 user
74 })
65 75
66 return res.json(getFormattedObjects(resultList.data, resultList.total)) 76 return res.json(getFormattedObjects(resultList.data, resultList.total))
67} 77}
@@ -94,10 +104,10 @@ async function deleteVideoAbuse (req: express.Request, res: express.Response) {
94} 104}
95 105
96async function reportVideoAbuse (req: express.Request, res: express.Response) { 106async function reportVideoAbuse (req: express.Request, res: express.Response) {
97 const videoInstance = res.locals.video 107 const videoInstance = res.locals.videoAll
98 const body: VideoAbuseCreate = req.body 108 const body: VideoAbuseCreate = req.body
99 109
100 const videoAbuse: VideoAbuseModel = await sequelizeTypescript.transaction(async t => { 110 const videoAbuse = await sequelizeTypescript.transaction(async t => {
101 const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) 111 const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
102 112
103 const abuseToCreate = { 113 const abuseToCreate = {
@@ -107,7 +117,7 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) {
107 state: VideoAbuseState.PENDING 117 state: VideoAbuseState.PENDING
108 } 118 }
109 119
110 const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) 120 const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(abuseToCreate, { transaction: t })
111 videoAbuseInstance.Video = videoInstance 121 videoAbuseInstance.Video = videoInstance
112 videoAbuseInstance.Account = reporterAccount 122 videoAbuseInstance.Account = reporterAccount
113 123
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts
index 9ff494def..2a667480d 100644
--- a/server/controllers/api/videos/blacklist.ts
+++ b/server/controllers/api/videos/blacklist.ts
@@ -1,5 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import { VideoBlacklist, UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared' 2import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { getFormattedObjects } from '../../../helpers/utils' 4import { getFormattedObjects } from '../../../helpers/utils'
5import { 5import {
@@ -11,15 +11,16 @@ import {
11 setBlacklistSort, 11 setBlacklistSort,
12 setDefaultPagination, 12 setDefaultPagination,
13 videosBlacklistAddValidator, 13 videosBlacklistAddValidator,
14 videosBlacklistFiltersValidator,
14 videosBlacklistRemoveValidator, 15 videosBlacklistRemoveValidator,
15 videosBlacklistUpdateValidator, 16 videosBlacklistUpdateValidator
16 videosBlacklistFiltersValidator
17} from '../../../middlewares' 17} from '../../../middlewares'
18import { VideoBlacklistModel } from '../../../models/video/video-blacklist' 18import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
19import { sequelizeTypescript } from '../../../initializers' 19import { sequelizeTypescript } from '../../../initializers'
20import { Notifier } from '../../../lib/notifier' 20import { Notifier } from '../../../lib/notifier'
21import { sendDeleteVideo } from '../../../lib/activitypub/send' 21import { sendDeleteVideo } from '../../../lib/activitypub/send'
22import { federateVideoIfNeeded } from '../../../lib/activitypub' 22import { federateVideoIfNeeded } from '../../../lib/activitypub'
23import { MVideoBlacklistVideo } from '@server/typings/models'
23 24
24const blacklistRouter = express.Router() 25const blacklistRouter = express.Router()
25 26
@@ -64,7 +65,7 @@ export {
64// --------------------------------------------------------------------------- 65// ---------------------------------------------------------------------------
65 66
66async function addVideoToBlacklist (req: express.Request, res: express.Response) { 67async function addVideoToBlacklist (req: express.Request, res: express.Response) {
67 const videoInstance = res.locals.video 68 const videoInstance = res.locals.videoAll
68 const body: VideoBlacklistCreate = req.body 69 const body: VideoBlacklistCreate = req.body
69 70
70 const toCreate = { 71 const toCreate = {
@@ -74,7 +75,7 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response)
74 type: VideoBlacklistType.MANUAL 75 type: VideoBlacklistType.MANUAL
75 } 76 }
76 77
77 const blacklist = await VideoBlacklistModel.create(toCreate) 78 const blacklist: MVideoBlacklistVideo = await VideoBlacklistModel.create(toCreate)
78 blacklist.Video = videoInstance 79 blacklist.Video = videoInstance
79 80
80 if (body.unfederate === true) { 81 if (body.unfederate === true) {
@@ -83,7 +84,7 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response)
83 84
84 Notifier.Instance.notifyOnVideoBlacklist(blacklist) 85 Notifier.Instance.notifyOnVideoBlacklist(blacklist)
85 86
86 logger.info('Video %s blacklisted.', res.locals.video.uuid) 87 logger.info('Video %s blacklisted.', videoInstance.uuid)
87 88
88 return res.type('json').status(204).end() 89 return res.type('json').status(204).end()
89} 90}
@@ -108,7 +109,7 @@ async function listBlacklist (req: express.Request, res: express.Response) {
108 109
109async function removeVideoFromBlacklistController (req: express.Request, res: express.Response) { 110async function removeVideoFromBlacklistController (req: express.Request, res: express.Response) {
110 const videoBlacklist = res.locals.videoBlacklist 111 const videoBlacklist = res.locals.videoBlacklist
111 const video = res.locals.video 112 const video = res.locals.videoAll
112 113
113 const videoBlacklistType = await sequelizeTypescript.transaction(async t => { 114 const videoBlacklistType = await sequelizeTypescript.transaction(async t => {
114 const unfederated = videoBlacklist.unfederated 115 const unfederated = videoBlacklist.unfederated
@@ -135,7 +136,7 @@ async function removeVideoFromBlacklistController (req: express.Request, res: ex
135 Notifier.Instance.notifyOnNewVideoIfNeeded(video) 136 Notifier.Instance.notifyOnNewVideoIfNeeded(video)
136 } 137 }
137 138
138 logger.info('Video %s removed from blacklist.', res.locals.video.uuid) 139 logger.info('Video %s removed from blacklist.', video.uuid)
139 140
140 return res.type('json').status(204).end() 141 return res.type('json').status(204).end()
141} 142}
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts
index 44c255232..37481d12f 100644
--- a/server/controllers/api/videos/captions.ts
+++ b/server/controllers/api/videos/captions.ts
@@ -10,6 +10,7 @@ import { federateVideoIfNeeded } from '../../../lib/activitypub'
10import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' 10import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
11import { CONFIG } from '../../../initializers/config' 11import { CONFIG } from '../../../initializers/config'
12import { sequelizeTypescript } from '../../../initializers/database' 12import { sequelizeTypescript } from '../../../initializers/database'
13import { MVideoCaptionVideo } from '@server/typings/models'
13 14
14const reqVideoCaptionAdd = createReqFiles( 15const reqVideoCaptionAdd = createReqFiles(
15 [ 'captionfile' ], 16 [ 'captionfile' ],
@@ -46,19 +47,19 @@ export {
46// --------------------------------------------------------------------------- 47// ---------------------------------------------------------------------------
47 48
48async function listVideoCaptions (req: express.Request, res: express.Response) { 49async function listVideoCaptions (req: express.Request, res: express.Response) {
49 const data = await VideoCaptionModel.listVideoCaptions(res.locals.video.id) 50 const data = await VideoCaptionModel.listVideoCaptions(res.locals.videoId.id)
50 51
51 return res.json(getFormattedObjects(data, data.length)) 52 return res.json(getFormattedObjects(data, data.length))
52} 53}
53 54
54async function addVideoCaption (req: express.Request, res: express.Response) { 55async function addVideoCaption (req: express.Request, res: express.Response) {
55 const videoCaptionPhysicalFile = req.files['captionfile'][0] 56 const videoCaptionPhysicalFile = req.files['captionfile'][0]
56 const video = res.locals.video 57 const video = res.locals.videoAll
57 58
58 const videoCaption = new VideoCaptionModel({ 59 const videoCaption = new VideoCaptionModel({
59 videoId: video.id, 60 videoId: video.id,
60 language: req.params.captionLanguage 61 language: req.params.captionLanguage
61 }) 62 }) as MVideoCaptionVideo
62 videoCaption.Video = video 63 videoCaption.Video = video
63 64
64 // Move physical file 65 // Move physical file
@@ -75,7 +76,7 @@ async function addVideoCaption (req: express.Request, res: express.Response) {
75} 76}
76 77
77async function deleteVideoCaption (req: express.Request, res: express.Response) { 78async function deleteVideoCaption (req: express.Request, res: express.Response) {
78 const video = res.locals.video 79 const video = res.locals.videoAll
79 const videoCaption = res.locals.videoCaption 80 const videoCaption = res.locals.videoCaption
80 81
81 await sequelizeTypescript.transaction(async t => { 82 await sequelizeTypescript.transaction(async t => {
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts
index bc6d81a7c..b2b06b170 100644
--- a/server/controllers/api/videos/comment.ts
+++ b/server/controllers/api/videos/comment.ts
@@ -27,9 +27,6 @@ import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../.
27import { AccountModel } from '../../../models/account/account' 27import { AccountModel } from '../../../models/account/account'
28import { Notifier } from '../../../lib/notifier' 28import { Notifier } from '../../../lib/notifier'
29import { Hooks } from '../../../lib/plugins/hooks' 29import { Hooks } from '../../../lib/plugins/hooks'
30import { ActorModel } from '../../../models/activitypub/actor'
31import { VideoChannelModel } from '../../../models/video/video-channel'
32import { VideoModel } from '../../../models/video/video'
33import { sendDeleteVideoComment } from '../../../lib/activitypub/send' 30import { sendDeleteVideoComment } from '../../../lib/activitypub/send'
34 31
35const auditLogger = auditLoggerFactory('comments') 32const auditLogger = auditLoggerFactory('comments')
@@ -75,7 +72,7 @@ export {
75// --------------------------------------------------------------------------- 72// ---------------------------------------------------------------------------
76 73
77async function listVideoThreads (req: express.Request, res: express.Response) { 74async function listVideoThreads (req: express.Request, res: express.Response) {
78 const video = res.locals.video 75 const video = res.locals.onlyVideo
79 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined 76 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
80 77
81 let resultList: ResultList<VideoCommentModel> 78 let resultList: ResultList<VideoCommentModel>
@@ -86,7 +83,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) {
86 start: req.query.start, 83 start: req.query.start,
87 count: req.query.count, 84 count: req.query.count,
88 sort: req.query.sort, 85 sort: req.query.sort,
89 user: user 86 user
90 }, 'filter:api.video-threads.list.params') 87 }, 'filter:api.video-threads.list.params')
91 88
92 resultList = await Hooks.wrapPromiseFun( 89 resultList = await Hooks.wrapPromiseFun(
@@ -105,7 +102,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) {
105} 102}
106 103
107async function listVideoThreadComments (req: express.Request, res: express.Response) { 104async function listVideoThreadComments (req: express.Request, res: express.Response) {
108 const video = res.locals.video 105 const video = res.locals.onlyVideo
109 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined 106 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
110 107
111 let resultList: ResultList<VideoCommentModel> 108 let resultList: ResultList<VideoCommentModel>
@@ -141,7 +138,7 @@ async function addVideoCommentThread (req: express.Request, res: express.Respons
141 return createVideoComment({ 138 return createVideoComment({
142 text: videoCommentInfo.text, 139 text: videoCommentInfo.text,
143 inReplyToComment: null, 140 inReplyToComment: null,
144 video: res.locals.video, 141 video: res.locals.videoAll,
145 account 142 account
146 }, t) 143 }, t)
147 }) 144 })
@@ -164,8 +161,8 @@ async function addVideoCommentReply (req: express.Request, res: express.Response
164 161
165 return createVideoComment({ 162 return createVideoComment({
166 text: videoCommentInfo.text, 163 text: videoCommentInfo.text,
167 inReplyToComment: res.locals.videoComment, 164 inReplyToComment: res.locals.videoCommentFull,
168 video: res.locals.video, 165 video: res.locals.videoAll,
169 account 166 account
170 }, t) 167 }, t)
171 }) 168 })
@@ -179,7 +176,7 @@ async function addVideoCommentReply (req: express.Request, res: express.Response
179} 176}
180 177
181async function removeVideoComment (req: express.Request, res: express.Response) { 178async function removeVideoComment (req: express.Request, res: express.Response) {
182 const videoCommentInstance = res.locals.videoComment 179 const videoCommentInstance = res.locals.videoCommentFull
183 180
184 await sequelizeTypescript.transaction(async t => { 181 await sequelizeTypescript.transaction(async t => {
185 await videoCommentInstance.destroy({ transaction: t }) 182 await videoCommentInstance.destroy({ transaction: t })
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
index 04c9b547b..28ced5836 100644
--- a/server/controllers/api/videos/import.ts
+++ b/server/controllers/api/videos/import.ts
@@ -1,6 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as magnetUtil from 'magnet-uri' 2import * as magnetUtil from 'magnet-uri'
3import 'multer'
4import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' 3import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
5import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' 4import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares'
6import { MIMETYPES } from '../../../initializers/constants' 5import { MIMETYPES } from '../../../initializers/constants'
@@ -15,7 +14,6 @@ import { VideoImportModel } from '../../../models/video/video-import'
15import { JobQueue } from '../../../lib/job-queue/job-queue' 14import { JobQueue } from '../../../lib/job-queue/job-queue'
16import { join } from 'path' 15import { join } from 'path'
17import { isArray } from '../../../helpers/custom-validators/misc' 16import { isArray } from '../../../helpers/custom-validators/misc'
18import { VideoChannelModel } from '../../../models/video/video-channel'
19import * as Bluebird from 'bluebird' 17import * as Bluebird from 'bluebird'
20import * as parseTorrent from 'parse-torrent' 18import * as parseTorrent from 'parse-torrent'
21import { getSecureTorrentName } from '../../../helpers/utils' 19import { getSecureTorrentName } from '../../../helpers/utils'
@@ -25,8 +23,16 @@ import { CONFIG } from '../../../initializers/config'
25import { sequelizeTypescript } from '../../../initializers/database' 23import { sequelizeTypescript } from '../../../initializers/database'
26import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' 24import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail'
27import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' 25import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
28import { ThumbnailModel } from '../../../models/video/thumbnail' 26import {
29import { UserModel } from '../../../models/account/user' 27 MChannelAccountDefault,
28 MThumbnail,
29 MUser,
30 MVideoAccountDefault,
31 MVideoTag,
32 MVideoThumbnailAccountDefault,
33 MVideoWithBlacklistLight
34} from '@server/typings/models'
35import { MVideoImport, MVideoImportFormattable } from '@server/typings/models/video/video-import'
30 36
31const auditLogger = auditLoggerFactory('video-imports') 37const auditLogger = auditLoggerFactory('video-imports')
32const videoImportsRouter = express.Router() 38const videoImportsRouter = express.Router()
@@ -184,8 +190,8 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You
184 category: body.category || importData.category, 190 category: body.category || importData.category,
185 licence: body.licence || importData.licence, 191 licence: body.licence || importData.licence,
186 language: body.language || undefined, 192 language: body.language || undefined,
187 commentsEnabled: body.commentsEnabled || true, 193 commentsEnabled: body.commentsEnabled !== false, // If the value is not "false", the default is "true"
188 downloadEnabled: body.downloadEnabled || true, 194 downloadEnabled: body.downloadEnabled !== false,
189 waitTranscoding: body.waitTranscoding || false, 195 waitTranscoding: body.waitTranscoding || false,
190 state: VideoState.TO_IMPORT, 196 state: VideoState.TO_IMPORT,
191 nsfw: body.nsfw || importData.nsfw || false, 197 nsfw: body.nsfw || importData.nsfw || false,
@@ -225,28 +231,28 @@ async function processPreview (req: express.Request, video: VideoModel) {
225} 231}
226 232
227function insertIntoDB (parameters: { 233function insertIntoDB (parameters: {
228 video: VideoModel, 234 video: MVideoThumbnailAccountDefault,
229 thumbnailModel: ThumbnailModel, 235 thumbnailModel: MThumbnail,
230 previewModel: ThumbnailModel, 236 previewModel: MThumbnail,
231 videoChannel: VideoChannelModel, 237 videoChannel: MChannelAccountDefault,
232 tags: string[], 238 tags: string[],
233 videoImportAttributes: Partial<VideoImportModel>, 239 videoImportAttributes: Partial<MVideoImport>,
234 user: UserModel 240 user: MUser
235}): Bluebird<VideoImportModel> { 241}): Bluebird<MVideoImportFormattable> {
236 const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters 242 const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters
237 243
238 return sequelizeTypescript.transaction(async t => { 244 return sequelizeTypescript.transaction(async t => {
239 const sequelizeOptions = { transaction: t } 245 const sequelizeOptions = { transaction: t }
240 246
241 // Save video object in database 247 // Save video object in database
242 const videoCreated = await video.save(sequelizeOptions) 248 const videoCreated = await video.save(sequelizeOptions) as (MVideoAccountDefault & MVideoWithBlacklistLight & MVideoTag)
243 videoCreated.VideoChannel = videoChannel 249 videoCreated.VideoChannel = videoChannel
244 250
245 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) 251 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
246 if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t) 252 if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
247 253
248 await autoBlacklistVideoIfNeeded({ 254 await autoBlacklistVideoIfNeeded({
249 video, 255 video: videoCreated,
250 user, 256 user,
251 notify: false, 257 notify: false,
252 isRemote: false, 258 isRemote: false,
@@ -268,7 +274,7 @@ function insertIntoDB (parameters: {
268 const videoImport = await VideoImportModel.create( 274 const videoImport = await VideoImportModel.create(
269 Object.assign({ videoId: videoCreated.id }, videoImportAttributes), 275 Object.assign({ videoId: videoCreated.id }, videoImportAttributes),
270 sequelizeOptions 276 sequelizeOptions
271 ) 277 ) as MVideoImportFormattable
272 videoImport.Video = videoCreated 278 videoImport.Video = videoCreated
273 279
274 return videoImport 280 return videoImport
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 155ca4678..19da504c7 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -63,6 +63,7 @@ import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../
63import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' 63import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
64import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding' 64import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding'
65import { Hooks } from '../../../lib/plugins/hooks' 65import { Hooks } from '../../../lib/plugins/hooks'
66import { MVideoDetails, MVideoFullLight } from '@server/typings/models'
66 67
67const auditLogger = auditLoggerFactory('videos') 68const auditLogger = auditLoggerFactory('videos')
68const videosRouter = express.Router() 69const videosRouter = express.Router()
@@ -185,7 +186,7 @@ async function addVideo (req: express.Request, res: express.Response) {
185 licence: videoInfo.licence, 186 licence: videoInfo.licence,
186 language: videoInfo.language, 187 language: videoInfo.language,
187 commentsEnabled: videoInfo.commentsEnabled || false, 188 commentsEnabled: videoInfo.commentsEnabled || false,
188 downloadEnabled: videoInfo.downloadEnabled || true, 189 downloadEnabled: videoInfo.downloadEnabled !== false, // If the value is not "false", the default is "true"
189 waitTranscoding: videoInfo.waitTranscoding || false, 190 waitTranscoding: videoInfo.waitTranscoding || false,
190 state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED, 191 state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED,
191 nsfw: videoInfo.nsfw || false, 192 nsfw: videoInfo.nsfw || false,
@@ -197,7 +198,7 @@ async function addVideo (req: express.Request, res: express.Response) {
197 originallyPublishedAt: videoInfo.originallyPublishedAt 198 originallyPublishedAt: videoInfo.originallyPublishedAt
198 } 199 }
199 200
200 const video = new VideoModel(videoData) 201 const video = new VideoModel(videoData) as MVideoDetails
201 video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object 202 video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
202 203
203 const videoFile = new VideoFileModel({ 204 const videoFile = new VideoFileModel({
@@ -238,7 +239,7 @@ async function addVideo (req: express.Request, res: express.Response) {
238 const { videoCreated } = await sequelizeTypescript.transaction(async t => { 239 const { videoCreated } = await sequelizeTypescript.transaction(async t => {
239 const sequelizeOptions = { transaction: t } 240 const sequelizeOptions = { transaction: t }
240 241
241 const videoCreated = await video.save(sequelizeOptions) 242 const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
242 243
243 await videoCreated.addAndSaveThumbnail(thumbnailModel, t) 244 await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
244 await videoCreated.addAndSaveThumbnail(previewModel, t) 245 await videoCreated.addAndSaveThumbnail(previewModel, t)
@@ -318,7 +319,7 @@ async function addVideo (req: express.Request, res: express.Response) {
318} 319}
319 320
320async function updateVideo (req: express.Request, res: express.Response) { 321async function updateVideo (req: express.Request, res: express.Response) {
321 const videoInstance = res.locals.video 322 const videoInstance = res.locals.videoAll
322 const videoFieldsSave = videoInstance.toJSON() 323 const videoFieldsSave = videoInstance.toJSON()
323 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) 324 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
324 const videoInfoToUpdate: VideoUpdate = req.body 325 const videoInfoToUpdate: VideoUpdate = req.body
@@ -371,7 +372,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
371 } 372 }
372 } 373 }
373 374
374 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) 375 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) as MVideoFullLight
375 376
376 if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t) 377 if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t)
377 if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t) 378 if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t)
@@ -447,7 +448,7 @@ async function getVideo (req: express.Request, res: express.Response) {
447 448
448 const video = await Hooks.wrapPromiseFun( 449 const video = await Hooks.wrapPromiseFun(
449 VideoModel.loadForGetAPI, 450 VideoModel.loadForGetAPI,
450 { id: res.locals.video.id, userId }, 451 { id: res.locals.onlyVideoWithRights.id, userId },
451 'filter:api.video.get.result' 452 'filter:api.video.get.result'
452 ) 453 )
453 454
@@ -460,7 +461,7 @@ async function getVideo (req: express.Request, res: express.Response) {
460} 461}
461 462
462async function viewVideo (req: express.Request, res: express.Response) { 463async function viewVideo (req: express.Request, res: express.Response) {
463 const videoInstance = res.locals.video 464 const videoInstance = res.locals.videoAll
464 465
465 const ip = req.ip 466 const ip = req.ip
466 const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid) 467 const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid)
@@ -483,7 +484,7 @@ async function viewVideo (req: express.Request, res: express.Response) {
483} 484}
484 485
485async function getVideoDescription (req: express.Request, res: express.Response) { 486async function getVideoDescription (req: express.Request, res: express.Response) {
486 const videoInstance = res.locals.video 487 const videoInstance = res.locals.videoAll
487 let description = '' 488 let description = ''
488 489
489 if (videoInstance.isOwned()) { 490 if (videoInstance.isOwned()) {
@@ -522,7 +523,7 @@ async function listVideos (req: express.Request, res: express.Response) {
522} 523}
523 524
524async function removeVideo (req: express.Request, res: express.Response) { 525async function removeVideo (req: express.Request, res: express.Response) {
525 const videoInstance = res.locals.video 526 const videoInstance = res.locals.videoAll
526 527
527 await sequelizeTypescript.transaction(async t => { 528 await sequelizeTypescript.transaction(async t => {
528 await videoInstance.destroy({ transaction: t }) 529 await videoInstance.destroy({ transaction: t })
diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts
index 5272c1385..abb34082e 100644
--- a/server/controllers/api/videos/ownership.ts
+++ b/server/controllers/api/videos/ownership.ts
@@ -18,6 +18,7 @@ import { getFormattedObjects } from '../../../helpers/utils'
18import { changeVideoChannelShare } from '../../../lib/activitypub' 18import { changeVideoChannelShare } from '../../../lib/activitypub'
19import { sendUpdateVideo } from '../../../lib/activitypub/send' 19import { sendUpdateVideo } from '../../../lib/activitypub/send'
20import { VideoModel } from '../../../models/video/video' 20import { VideoModel } from '../../../models/video/video'
21import { MVideoFullLight } from '@server/typings/models'
21 22
22const ownershipVideoRouter = express.Router() 23const ownershipVideoRouter = express.Router()
23 24
@@ -56,7 +57,7 @@ export {
56// --------------------------------------------------------------------------- 57// ---------------------------------------------------------------------------
57 58
58async function giveVideoOwnership (req: express.Request, res: express.Response) { 59async function giveVideoOwnership (req: express.Request, res: express.Response) {
59 const videoInstance = res.locals.video 60 const videoInstance = res.locals.videoAll
60 const initiatorAccountId = res.locals.oauth.token.User.Account.id 61 const initiatorAccountId = res.locals.oauth.token.User.Account.id
61 const nextOwner = res.locals.nextOwner 62 const nextOwner = res.locals.nextOwner
62 63
@@ -107,7 +108,7 @@ async function acceptOwnership (req: express.Request, res: express.Response) {
107 108
108 targetVideo.channelId = channel.id 109 targetVideo.channelId = channel.id
109 110
110 const targetVideoUpdated = await targetVideo.save({ transaction: t }) 111 const targetVideoUpdated = await targetVideo.save({ transaction: t }) as MVideoFullLight
111 targetVideoUpdated.VideoChannel = channel 112 targetVideoUpdated.VideoChannel = channel
112 113
113 if (targetVideoUpdated.privacy !== VideoPrivacy.PRIVATE && targetVideoUpdated.state === VideoState.PUBLISHED) { 114 if (targetVideoUpdated.privacy !== VideoPrivacy.PRIVATE && targetVideoUpdated.state === VideoState.PUBLISHED) {
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts
index b65babedf..3d2f3d728 100644
--- a/server/controllers/api/videos/rate.ts
+++ b/server/controllers/api/videos/rate.ts
@@ -27,7 +27,7 @@ export {
27async function rateVideo (req: express.Request, res: express.Response) { 27async function rateVideo (req: express.Request, res: express.Response) {
28 const body: UserVideoRateUpdate = req.body 28 const body: UserVideoRateUpdate = req.body
29 const rateType = body.rating 29 const rateType = body.rating
30 const videoInstance = res.locals.video 30 const videoInstance = res.locals.videoAll
31 const userAccount = res.locals.oauth.token.User.Account 31 const userAccount = res.locals.oauth.token.User.Account
32 32
33 await sequelizeTypescript.transaction(async t => { 33 await sequelizeTypescript.transaction(async t => {
diff --git a/server/controllers/api/videos/watching.ts b/server/controllers/api/videos/watching.ts
index dcd1f070d..036e16f3a 100644
--- a/server/controllers/api/videos/watching.ts
+++ b/server/controllers/api/videos/watching.ts
@@ -23,7 +23,7 @@ async function userWatchVideo (req: express.Request, res: express.Response) {
23 const user = res.locals.oauth.token.User 23 const user = res.locals.oauth.token.User
24 24
25 const body: UserWatchingVideo = req.body 25 const body: UserWatchingVideo = req.body
26 const { id: videoId } = res.locals.video as { id: number } 26 const { id: videoId } = res.locals.videoId
27 27
28 await UserVideoHistoryModel.upsert({ 28 await UserVideoHistoryModel.upsert({
29 videoId, 29 videoId,
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts
index d3f581615..468f7a668 100644
--- a/server/controllers/feeds.ts
+++ b/server/controllers/feeds.ts
@@ -43,7 +43,7 @@ export {
43async function generateVideoCommentsFeed (req: express.Request, res: express.Response) { 43async function generateVideoCommentsFeed (req: express.Request, res: express.Response) {
44 const start = 0 44 const start = 0
45 45
46 const video = res.locals.video 46 const video = res.locals.videoAll
47 const videoId: number = video ? video.id : undefined 47 const videoId: number = video ? video.id : undefined
48 48
49 const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId) 49 const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId)
diff --git a/server/controllers/services.ts b/server/controllers/services.ts
index c1c53c3fc..ec057235f 100644
--- a/server/controllers/services.ts
+++ b/server/controllers/services.ts
@@ -23,7 +23,7 @@ export {
23// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
24 24
25function generateOEmbed (req: express.Request, res: express.Response) { 25function generateOEmbed (req: express.Request, res: express.Response) {
26 const video = res.locals.video 26 const video = res.locals.videoAll
27 const webserverUrl = WEBSERVER.URL 27 const webserverUrl = WEBSERVER.URL
28 const maxHeight = parseInt(req.query.maxheight, 10) 28 const maxHeight = parseInt(req.query.maxheight, 10)
29 const maxWidth = parseInt(req.query.maxwidth, 10) 29 const maxWidth = parseInt(req.query.maxwidth, 10)
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 8979ef5f3..0f4772310 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -226,14 +226,14 @@ async function generateNodeinfo (req: express.Request, res: express.Response) {
226 return res.send(json).end() 226 return res.send(json).end()
227} 227}
228 228
229async function downloadTorrent (req: express.Request, res: express.Response, next: express.NextFunction) { 229async function downloadTorrent (req: express.Request, res: express.Response) {
230 const { video, videoFile } = getVideoAndFile(req, res) 230 const { video, videoFile } = getVideoAndFile(req, res)
231 if (!videoFile) return res.status(404).end() 231 if (!videoFile) return res.status(404).end()
232 232
233 return res.download(video.getTorrentFilePath(videoFile), `${video.name}-${videoFile.resolution}p.torrent`) 233 return res.download(video.getTorrentFilePath(videoFile), `${video.name}-${videoFile.resolution}p.torrent`)
234} 234}
235 235
236async function downloadVideoFile (req: express.Request, res: express.Response, next: express.NextFunction) { 236async function downloadVideoFile (req: express.Request, res: express.Response) {
237 const { video, videoFile } = getVideoAndFile(req, res) 237 const { video, videoFile } = getVideoAndFile(req, res)
238 if (!videoFile) return res.status(404).end() 238 if (!videoFile) return res.status(404).end()
239 239
@@ -242,7 +242,7 @@ async function downloadVideoFile (req: express.Request, res: express.Response, n
242 242
243function getVideoAndFile (req: express.Request, res: express.Response) { 243function getVideoAndFile (req: express.Request, res: express.Response) {
244 const resolution = parseInt(req.params.resolution, 10) 244 const resolution = parseInt(req.params.resolution, 10)
245 const video = res.locals.video 245 const video = res.locals.videoAll
246 246
247 const videoFile = video.VideoFiles.find(f => f.resolution === resolution) 247 const videoFile = video.VideoFiles.find(f => f.resolution === resolution)
248 248
diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts
index f2ba3c826..fc9575160 100644
--- a/server/controllers/webfinger.ts
+++ b/server/controllers/webfinger.ts
@@ -18,7 +18,7 @@ export {
18// --------------------------------------------------------------------------- 18// ---------------------------------------------------------------------------
19 19
20function webfingerController (req: express.Request, res: express.Response) { 20function webfingerController (req: express.Request, res: express.Response) {
21 const actor = res.locals.actor 21 const actor = res.locals.actorFull
22 22
23 const json = { 23 const json = {
24 subject: req.query.resource, 24 subject: req.query.resource,
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts
index 951a25669..97c809a0c 100644
--- a/server/helpers/activitypub.ts
+++ b/server/helpers/activitypub.ts
@@ -7,6 +7,7 @@ import { ActorModel } from '../models/activitypub/actor'
7import { signJsonLDObject } from './peertube-crypto' 7import { signJsonLDObject } from './peertube-crypto'
8import { pageToStartAndCount } from './core-utils' 8import { pageToStartAndCount } from './core-utils'
9import { parse } from 'url' 9import { parse } from 'url'
10import { MActor } from '../typings/models'
10 11
11function activityPubContextify <T> (data: T) { 12function activityPubContextify <T> (data: T) {
12 return Object.assign(data, { 13 return Object.assign(data, {
@@ -143,7 +144,7 @@ async function activityPubCollectionPagination (baseUrl: string, handler: Activi
143 144
144} 145}
145 146
146function buildSignedActivity (byActor: ActorModel, data: Object) { 147function buildSignedActivity (byActor: MActor, data: Object) {
147 const activity = activityPubContextify(data) 148 const activity = activityPubContextify(data)
148 149
149 return signJsonLDObject(byActor, activity) as Promise<Activity> 150 return signJsonLDObject(byActor, activity) as Promise<Activity>
diff --git a/server/helpers/actor.ts b/server/helpers/actor.ts
index 12a7ace9f..117548a60 100644
--- a/server/helpers/actor.ts
+++ b/server/helpers/actor.ts
@@ -1,10 +1,13 @@
1import { ActorModel } from '../models/activitypub/actor' 1import { ActorModel } from '../models/activitypub/actor'
2import * as Bluebird from 'bluebird'
3import { MActorFull, MActorAccountChannelId } from '../typings/models'
2 4
3type ActorFetchByUrlType = 'all' | 'actor-and-association-ids' 5type ActorFetchByUrlType = 'all' | 'association-ids'
4function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType) { 6
7function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType): Bluebird<MActorFull | MActorAccountChannelId> {
5 if (fetchType === 'all') return ActorModel.loadByUrlAndPopulateAccountAndChannel(url) 8 if (fetchType === 'all') return ActorModel.loadByUrlAndPopulateAccountAndChannel(url)
6 9
7 if (fetchType === 'actor-and-association-ids') return ActorModel.loadByUrl(url) 10 if (fetchType === 'association-ids') return ActorModel.loadByUrl(url)
8} 11}
9 12
10export { 13export {
diff --git a/server/helpers/captions-utils.ts b/server/helpers/captions-utils.ts
index 7174d4654..2830ae017 100644
--- a/server/helpers/captions-utils.ts
+++ b/server/helpers/captions-utils.ts
@@ -1,10 +1,10 @@
1import { join } from 'path' 1import { join } from 'path'
2import { CONFIG } from '../initializers/config' 2import { CONFIG } from '../initializers/config'
3import { VideoCaptionModel } from '../models/video/video-caption'
4import * as srt2vtt from 'srt-to-vtt' 3import * as srt2vtt from 'srt-to-vtt'
5import { createReadStream, createWriteStream, remove, move } from 'fs-extra' 4import { createReadStream, createWriteStream, move, remove } from 'fs-extra'
5import { MVideoCaptionFormattable } from '@server/typings/models'
6 6
7async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: VideoCaptionModel) { 7async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: MVideoCaptionFormattable) {
8 const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR 8 const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR
9 const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) 9 const destination = join(videoCaptionsDir, videoCaption.getCaptionName())
10 10
diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts
index a3bceb047..cb07fa3b2 100644
--- a/server/helpers/custom-jsonld-signature.ts
+++ b/server/helpers/custom-jsonld-signature.ts
@@ -1,6 +1,5 @@
1import * as AsyncLRU from 'async-lru' 1import * as AsyncLRU from 'async-lru'
2import * as jsonld from 'jsonld' 2import * as jsonld from 'jsonld'
3import * as jsig from 'jsonld-signatures'
4import { logger } from './logger' 3import { logger } from './logger'
5 4
6const CACHE = { 5const CACHE = {
@@ -79,6 +78,4 @@ jsonld.documentLoader = (url, cb) => {
79 lru.get(url, cb) 78 lru.get(url, cb)
80} 79}
81 80
82jsig.use('jsonld', jsonld) 81export { jsonld }
83
84export { jsig, jsonld }
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts
index deb331abb..55bc8cc96 100644
--- a/server/helpers/custom-validators/activitypub/actor.ts
+++ b/server/helpers/custom-validators/activitypub/actor.ts
@@ -27,7 +27,7 @@ function isActorPublicKeyValid (publicKey: string) {
27 validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) 27 validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY)
28} 28}
29 29
30const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.]' 30const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]'
31const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) 31const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`)
32function isActorPreferredUsernameValid (preferredUsername: string) { 32function isActorPreferredUsernameValid (preferredUsername: string) {
33 return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp) 33 return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp)
@@ -46,19 +46,20 @@ function isActorObjectValid (actor: any) {
46 return exists(actor) && 46 return exists(actor) &&
47 isActivityPubUrlValid(actor.id) && 47 isActivityPubUrlValid(actor.id) &&
48 isActorTypeValid(actor.type) && 48 isActorTypeValid(actor.type) &&
49 isActivityPubUrlValid(actor.following) &&
50 isActivityPubUrlValid(actor.followers) &&
51 isActivityPubUrlValid(actor.inbox) && 49 isActivityPubUrlValid(actor.inbox) &&
52 isActivityPubUrlValid(actor.outbox) &&
53 isActorPreferredUsernameValid(actor.preferredUsername) && 50 isActorPreferredUsernameValid(actor.preferredUsername) &&
54 isActivityPubUrlValid(actor.url) && 51 isActivityPubUrlValid(actor.url) &&
55 isActorPublicKeyObjectValid(actor.publicKey) && 52 isActorPublicKeyObjectValid(actor.publicKey) &&
56 isActorEndpointsObjectValid(actor.endpoints) && 53 isActorEndpointsObjectValid(actor.endpoints) &&
57 setValidAttributedTo(actor) &&
58 54
59 // If this is not an account, it should be attributed to an account 55 (!actor.outbox || isActivityPubUrlValid(actor.outbox)) &&
56 (!actor.following || isActivityPubUrlValid(actor.following)) &&
57 (!actor.followers || isActivityPubUrlValid(actor.followers)) &&
58
59 setValidAttributedTo(actor) &&
60 // If this is a group (a channel), it should be attributed to an account
60 // In PeerTube we use this to attach a video channel to a specific account 61 // In PeerTube we use this to attach a video channel to a specific account
61 (actor.type === 'Person' || actor.attributedTo.length !== 0) 62 (actor.type !== 'Group' || actor.attributedTo.length !== 0)
62} 63}
63 64
64function isActorFollowingCountValid (value: string) { 65function isActorFollowingCountValid (value: string) {
diff --git a/server/helpers/custom-validators/video-ownership.ts b/server/helpers/custom-validators/video-ownership.ts
index a7771e07b..9570b2799 100644
--- a/server/helpers/custom-validators/video-ownership.ts
+++ b/server/helpers/custom-validators/video-ownership.ts
@@ -1,10 +1,10 @@
1import { Response } from 'express' 1import { Response } from 'express'
2import * as validator from 'validator'
3import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership' 2import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership'
4import { UserModel } from '../../models/account/user' 3import { MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership'
4import { MUserId } from '@server/typings/models'
5 5
6export async function doesChangeVideoOwnershipExist (id: string, res: Response): Promise<boolean> { 6export async function doesChangeVideoOwnershipExist (id: number, res: Response) {
7 const videoChangeOwnership = await loadVideoChangeOwnership(id) 7 const videoChangeOwnership = await VideoChangeOwnershipModel.load(id)
8 8
9 if (!videoChangeOwnership) { 9 if (!videoChangeOwnership) {
10 res.status(404) 10 res.status(404)
@@ -18,19 +18,7 @@ export async function doesChangeVideoOwnershipExist (id: string, res: Response):
18 return true 18 return true
19} 19}
20 20
21async function loadVideoChangeOwnership (id: string): Promise<VideoChangeOwnershipModel | undefined> { 21export function checkUserCanTerminateOwnershipChange (user: MUserId, videoChangeOwnership: MVideoChangeOwnershipFull, res: Response) {
22 if (validator.isInt(id)) {
23 return VideoChangeOwnershipModel.load(parseInt(id, 10))
24 }
25
26 return undefined
27}
28
29export function checkUserCanTerminateOwnershipChange (
30 user: UserModel,
31 videoChangeOwnership: VideoChangeOwnershipModel,
32 res: Response
33): boolean {
34 if (videoChangeOwnership.NextOwner.userId === user.id) { 22 if (videoChangeOwnership.NextOwner.userId === user.id) {
35 return true 23 return true
36 } 24 }
diff --git a/server/helpers/middlewares/accounts.ts b/server/helpers/middlewares/accounts.ts
index 791022b97..f5aa0bada 100644
--- a/server/helpers/middlewares/accounts.ts
+++ b/server/helpers/middlewares/accounts.ts
@@ -1,6 +1,7 @@
1import { Response } from 'express' 1import { Response } from 'express'
2import { AccountModel } from '../../models/account/account' 2import { AccountModel } from '../../models/account/account'
3import * as Bluebird from 'bluebird' 3import * as Bluebird from 'bluebird'
4import { MAccountDefault } from '../../typings/models'
4 5
5function doesAccountIdExist (id: number, res: Response, sendNotFound = true) { 6function doesAccountIdExist (id: number, res: Response, sendNotFound = true) {
6 const promise = AccountModel.load(id) 7 const promise = AccountModel.load(id)
@@ -15,10 +16,12 @@ function doesLocalAccountNameExist (name: string, res: Response, sendNotFound =
15} 16}
16 17
17function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { 18function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) {
18 return doesAccountExist(AccountModel.loadByNameWithHost(nameWithDomain), res, sendNotFound) 19 const promise = AccountModel.loadByNameWithHost(nameWithDomain)
20
21 return doesAccountExist(promise, res, sendNotFound)
19} 22}
20 23
21async function doesAccountExist (p: Bluebird<AccountModel>, res: Response, sendNotFound: boolean) { 24async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, sendNotFound: boolean) {
22 const account = await p 25 const account = await p
23 26
24 if (!account) { 27 if (!account) {
diff --git a/server/helpers/middlewares/video-abuses.ts b/server/helpers/middlewares/video-abuses.ts
index b23f1f021..1b573ca37 100644
--- a/server/helpers/middlewares/video-abuses.ts
+++ b/server/helpers/middlewares/video-abuses.ts
@@ -1,41 +1,23 @@
1import * as express from 'express' 1import { Response } from 'express'
2import { VideoChannelModel } from '../../models/video/video-channel' 2import { VideoAbuseModel } from '../../models/video/video-abuse'
3 3
4async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { 4async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) {
5 const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) 5 const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId)
6 6
7 return processVideoChannelExist(videoChannel, res) 7 if (videoAbuse === null) {
8}
9
10async function doesVideoChannelIdExist (id: number, res: express.Response) {
11 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
12
13 return processVideoChannelExist(videoChannel, res)
14}
15
16async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) {
17 const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain)
18
19 return processVideoChannelExist(videoChannel, res)
20}
21
22// ---------------------------------------------------------------------------
23
24export {
25 doesLocalVideoChannelNameExist,
26 doesVideoChannelIdExist,
27 doesVideoChannelNameWithHostExist
28}
29
30function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) {
31 if (!videoChannel) {
32 res.status(404) 8 res.status(404)
33 .json({ error: 'Video channel not found' }) 9 .json({ error: 'Video abuse not found' })
34 .end() 10 .end()
35 11
36 return false 12 return false
37 } 13 }
38 14
39 res.locals.videoChannel = videoChannel 15 res.locals.videoAbuse = videoAbuse
40 return true 16 return true
41} 17}
18
19// ---------------------------------------------------------------------------
20
21export {
22 doesVideoAbuseExist
23}
diff --git a/server/helpers/middlewares/video-captions.ts b/server/helpers/middlewares/video-captions.ts
index dc3d0144b..1b2513b60 100644
--- a/server/helpers/middlewares/video-captions.ts
+++ b/server/helpers/middlewares/video-captions.ts
@@ -1,8 +1,8 @@
1import { VideoModel } from '../../models/video/video'
2import { Response } from 'express' 1import { Response } from 'express'
3import { VideoCaptionModel } from '../../models/video/video-caption' 2import { VideoCaptionModel } from '../../models/video/video-caption'
3import { MVideoId } from '@server/typings/models'
4 4
5async function doesVideoCaptionExist (video: VideoModel, language: string, res: Response) { 5async function doesVideoCaptionExist (video: MVideoId, language: string, res: Response) {
6 const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) 6 const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language)
7 7
8 if (!videoCaption) { 8 if (!videoCaption) {
diff --git a/server/helpers/middlewares/video-channels.ts b/server/helpers/middlewares/video-channels.ts
index 1b573ca37..1595ecd94 100644
--- a/server/helpers/middlewares/video-channels.ts
+++ b/server/helpers/middlewares/video-channels.ts
@@ -1,23 +1,42 @@
1import { Response } from 'express' 1import * as express from 'express'
2import { VideoAbuseModel } from '../../models/video/video-abuse' 2import { VideoChannelModel } from '../../models/video/video-channel'
3import { MChannelAccountDefault } from '@server/typings/models'
3 4
4async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) { 5async function doesLocalVideoChannelNameExist (name: string, res: express.Response) {
5 const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) 6 const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name)
6 7
7 if (videoAbuse === null) { 8 return processVideoChannelExist(videoChannel, res)
8 res.status(404) 9}
9 .json({ error: 'Video abuse not found' })
10 .end()
11 10
12 return false 11async function doesVideoChannelIdExist (id: number, res: express.Response) {
13 } 12 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
14 13
15 res.locals.videoAbuse = videoAbuse 14 return processVideoChannelExist(videoChannel, res)
16 return true 15}
16
17async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) {
18 const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain)
19
20 return processVideoChannelExist(videoChannel, res)
17} 21}
18 22
19// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
20 24
21export { 25export {
22 doesVideoAbuseExist 26 doesLocalVideoChannelNameExist,
27 doesVideoChannelIdExist,
28 doesVideoChannelNameWithHostExist
29}
30
31function processVideoChannelExist (videoChannel: MChannelAccountDefault, res: express.Response) {
32 if (!videoChannel) {
33 res.status(404)
34 .json({ error: 'Video channel not found' })
35 .end()
36
37 return false
38 }
39
40 res.locals.videoChannel = videoChannel
41 return true
23} 42}
diff --git a/server/helpers/middlewares/video-playlists.ts b/server/helpers/middlewares/video-playlists.ts
index 735bf362f..8e7484483 100644
--- a/server/helpers/middlewares/video-playlists.ts
+++ b/server/helpers/middlewares/video-playlists.ts
@@ -1,11 +1,31 @@
1import * as express from 'express' 1import * as express from 'express'
2import { VideoPlaylistModel } from '../../models/video/video-playlist' 2import { VideoPlaylistModel } from '../../models/video/video-playlist'
3import { MVideoPlaylist } from '../../typings/models/video/video-playlist'
3 4
4async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: 'summary' | 'all' = 'summary') { 5export type VideoPlaylistFetchType = 'summary' | 'all'
5 const videoPlaylist = fetchType === 'summary' 6async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: VideoPlaylistFetchType = 'summary') {
6 ? await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined) 7 if (fetchType === 'summary') {
7 : await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined) 8 const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined)
9 res.locals.videoPlaylistSummary = videoPlaylist
8 10
11 return handleVideoPlaylist(videoPlaylist, res)
12 }
13
14 const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined)
15 res.locals.videoPlaylistFull = videoPlaylist
16
17 return handleVideoPlaylist(videoPlaylist, res)
18}
19
20// ---------------------------------------------------------------------------
21
22export {
23 doesVideoPlaylistExist
24}
25
26// ---------------------------------------------------------------------------
27
28function handleVideoPlaylist (videoPlaylist: MVideoPlaylist, res: express.Response) {
9 if (!videoPlaylist) { 29 if (!videoPlaylist) {
10 res.status(404) 30 res.status(404)
11 .json({ error: 'Video playlist not found' }) 31 .json({ error: 'Video playlist not found' })
@@ -14,12 +34,5 @@ async function doesVideoPlaylistExist (id: number | string, res: express.Respons
14 return false 34 return false
15 } 35 }
16 36
17 res.locals.videoPlaylist = videoPlaylist
18 return true 37 return true
19} 38}
20
21// ---------------------------------------------------------------------------
22
23export {
24 doesVideoPlaylistExist
25}
diff --git a/server/helpers/middlewares/videos.ts b/server/helpers/middlewares/videos.ts
index ceb1058ec..74f529804 100644
--- a/server/helpers/middlewares/videos.ts
+++ b/server/helpers/middlewares/videos.ts
@@ -1,9 +1,8 @@
1import { Response } from 'express' 1import { Response } from 'express'
2import { fetchVideo, VideoFetchType } from '../video' 2import { fetchVideo, VideoFetchType } from '../video'
3import { UserModel } from '../../models/account/user'
4import { UserRight } from '../../../shared/models/users' 3import { UserRight } from '../../../shared/models/users'
5import { VideoChannelModel } from '../../models/video/video-channel' 4import { VideoChannelModel } from '../../models/video/video-channel'
6import { VideoModel } from '../../models/video/video' 5import { MUser, MUserAccountId, MVideoAccountLight, MVideoFullLight, MVideoThumbnail, MVideoWithRights } from '@server/typings/models'
7 6
8async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { 7async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') {
9 const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined 8 const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
@@ -18,11 +17,28 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi
18 return false 17 return false
19 } 18 }
20 19
21 if (fetchType !== 'none') res.locals.video = video 20 switch (fetchType) {
21 case 'all':
22 res.locals.videoAll = video as MVideoFullLight
23 break
24
25 case 'id':
26 res.locals.videoId = video
27 break
28
29 case 'only-video':
30 res.locals.onlyVideo = video as MVideoThumbnail
31 break
32
33 case 'only-video-with-rights':
34 res.locals.onlyVideoWithRights = video as MVideoWithRights
35 break
36 }
37
22 return true 38 return true
23} 39}
24 40
25async function doesVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) { 41async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) {
26 if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { 42 if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) {
27 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) 43 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
28 if (videoChannel === null) { 44 if (videoChannel === null) {
@@ -50,7 +66,7 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: UserMode
50 return true 66 return true
51} 67}
52 68
53function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: Response) { 69function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response) {
54 // Retrieve the user who did the request 70 // Retrieve the user who did the request
55 if (video.isOwned() === false) { 71 if (video.isOwned() === false) {
56 res.status(403) 72 res.status(403)
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts
index 1424949d0..9eb782302 100644
--- a/server/helpers/peertube-crypto.ts
+++ b/server/helpers/peertube-crypto.ts
@@ -1,13 +1,13 @@
1import { Request } from 'express' 1import { Request } from 'express'
2import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' 2import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
3import { ActorModel } from '../models/activitypub/actor'
4import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' 3import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils'
5import { jsig, jsonld } from './custom-jsonld-signature' 4import { jsonld } from './custom-jsonld-signature'
6import { logger } from './logger' 5import { logger } from './logger'
7import { cloneDeep } from 'lodash' 6import { cloneDeep } from 'lodash'
8import { createVerify } from 'crypto' 7import { createSign, createVerify } from 'crypto'
9import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' 8import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils'
10import * as bcrypt from 'bcrypt' 9import * as bcrypt from 'bcrypt'
10import { MActor } from '../typings/models'
11 11
12const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) 12const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
13const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) 13const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
@@ -46,7 +46,7 @@ function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean {
46 return true 46 return true
47} 47}
48 48
49function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean { 49function isHTTPSignatureVerified (httpSignatureParsed: any, actor: MActor): boolean {
50 return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true 50 return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true
51} 51}
52 52
@@ -56,70 +56,21 @@ function parseHTTPSignature (req: Request, clockSkew?: number) {
56 56
57// JSONLD 57// JSONLD
58 58
59async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any): Promise<boolean> { 59function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
60 if (signedDocument.signature.type === 'RsaSignature2017') { 60 if (signedDocument.signature.type === 'RsaSignature2017') {
61 // Mastodon algorithm 61 return isJsonLDRSA2017Verified(fromActor, signedDocument)
62 const res = await isJsonLDRSA2017Verified(fromActor, signedDocument)
63 // Success? If no, try with our library
64 if (res === true) return true
65 } 62 }
66 63
67 const publicKeyObject = { 64 logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument)
68 '@context': jsig.SECURITY_CONTEXT_URL,
69 id: fromActor.url,
70 type: 'CryptographicKey',
71 owner: fromActor.url,
72 publicKeyPem: fromActor.publicKey
73 }
74
75 const publicKeyOwnerObject = {
76 '@context': jsig.SECURITY_CONTEXT_URL,
77 id: fromActor.url,
78 publicKey: [ publicKeyObject ]
79 }
80 65
81 const options = { 66 return Promise.resolve(false)
82 publicKey: publicKeyObject,
83 publicKeyOwner: publicKeyOwnerObject
84 }
85
86 return jsig.promises
87 .verify(signedDocument, options)
88 .then((result: { verified: boolean }) => result.verified)
89 .catch(err => {
90 logger.error('Cannot check signature.', { err })
91 return false
92 })
93} 67}
94 68
95// Backward compatibility with "other" implementations 69// Backward compatibility with "other" implementations
96async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: any) { 70async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) {
97 function hash (obj: any): Promise<any> {
98 return jsonld.promises
99 .normalize(obj, {
100 algorithm: 'URDNA2015',
101 format: 'application/n-quads'
102 })
103 .then(res => sha256(res))
104 }
105
106 const signatureCopy = cloneDeep(signedDocument.signature)
107 Object.assign(signatureCopy, {
108 '@context': [
109 'https://w3id.org/security/v1',
110 { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
111 ]
112 })
113 delete signatureCopy.type
114 delete signatureCopy.id
115 delete signatureCopy.signatureValue
116
117 const docWithoutSignature = cloneDeep(signedDocument)
118 delete docWithoutSignature.signature
119
120 const [ documentHash, optionsHash ] = await Promise.all([ 71 const [ documentHash, optionsHash ] = await Promise.all([
121 hash(docWithoutSignature), 72 createDocWithoutSignatureHash(signedDocument),
122 hash(signatureCopy) 73 createSignatureHash(signedDocument.signature)
123 ]) 74 ])
124 75
125 const toVerify = optionsHash + documentHash 76 const toVerify = optionsHash + documentHash
@@ -130,14 +81,27 @@ async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: a
130 return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') 81 return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
131} 82}
132 83
133function signJsonLDObject (byActor: ActorModel, data: any) { 84async function signJsonLDObject (byActor: MActor, data: any) {
134 const options = { 85 const signature = {
135 privateKeyPem: byActor.privateKey, 86 type: 'RsaSignature2017',
136 creator: byActor.url, 87 creator: byActor.url,
137 algorithm: 'RsaSignature2017' 88 created: new Date().toISOString()
138 } 89 }
139 90
140 return jsig.promises.sign(data, options) 91 const [ documentHash, optionsHash ] = await Promise.all([
92 createDocWithoutSignatureHash(data),
93 createSignatureHash(signature)
94 ])
95
96 const toSign = optionsHash + documentHash
97
98 const sign = createSign('RSA-SHA256')
99 sign.update(toSign, 'utf8')
100
101 const signatureValue = sign.sign(byActor.privateKey, 'base64')
102 Object.assign(signature, { signatureValue })
103
104 return Object.assign(data, { signature })
141} 105}
142 106
143// --------------------------------------------------------------------------- 107// ---------------------------------------------------------------------------
@@ -154,3 +118,35 @@ export {
154} 118}
155 119
156// --------------------------------------------------------------------------- 120// ---------------------------------------------------------------------------
121
122function hash (obj: any): Promise<any> {
123 return jsonld.promises
124 .normalize(obj, {
125 algorithm: 'URDNA2015',
126 format: 'application/n-quads'
127 })
128 .then(res => sha256(res))
129}
130
131function createSignatureHash (signature: any) {
132 const signatureCopy = cloneDeep(signature)
133 Object.assign(signatureCopy, {
134 '@context': [
135 'https://w3id.org/security/v1',
136 { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
137 ]
138 })
139
140 delete signatureCopy.type
141 delete signatureCopy.id
142 delete signatureCopy.signatureValue
143
144 return hash(signatureCopy)
145}
146
147function createDocWithoutSignatureHash (doc: any) {
148 const docWithoutSignature = cloneDeep(doc)
149 delete docWithoutSignature.signature
150
151 return hash(docWithoutSignature)
152}
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index 1464b1477..ba07eaaf3 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -19,7 +19,10 @@ async function generateRandomString (size: number) {
19 return raw.toString('hex') 19 return raw.toString('hex')
20} 20}
21 21
22interface FormattableToJSON<U, V> { toFormattedJSON (args?: U): V } 22interface FormattableToJSON<U, V> {
23 toFormattedJSON (args?: U): V
24}
25
23function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: T[], objectsTotal: number, formattedArg?: U) { 26function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: T[], objectsTotal: number, formattedArg?: U) {
24 const formattedObjects = objects.map(o => o.toFormattedJSON(formattedArg)) 27 const formattedObjects = objects.map(o => o.toFormattedJSON(formattedArg))
25 28
diff --git a/server/helpers/video.ts b/server/helpers/video.ts
index c90fe06c7..d066e2b1f 100644
--- a/server/helpers/video.ts
+++ b/server/helpers/video.ts
@@ -1,8 +1,30 @@
1import { VideoModel } from '../models/video/video' 1import { VideoModel } from '../models/video/video'
2import * as Bluebird from 'bluebird'
3import {
4 MVideoAccountLightBlacklistAllFiles,
5 MVideoFullLight,
6 MVideoIdThumbnail,
7 MVideoThumbnail,
8 MVideoWithRights
9} from '@server/typings/models'
10import { Response } from 'express'
2 11
3type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' 12type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none'
4 13
5function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) { 14function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight>
15function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail>
16function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights>
17function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail>
18function fetchVideo (
19 id: number | string,
20 fetchType: VideoFetchType,
21 userId?: number
22): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail>
23function fetchVideo (
24 id: number | string,
25 fetchType: VideoFetchType,
26 userId?: number
27): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> {
6 if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) 28 if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId)
7 29
8 if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) 30 if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id)
@@ -13,15 +35,29 @@ function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: nu
13} 35}
14 36
15type VideoFetchByUrlType = 'all' | 'only-video' 37type VideoFetchByUrlType = 'all' | 'only-video'
16function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType) { 38
39function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountLightBlacklistAllFiles>
40function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail>
41function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail>
42function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> {
17 if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url) 43 if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url)
18 44
19 if (fetchType === 'only-video') return VideoModel.loadByUrl(url) 45 if (fetchType === 'only-video') return VideoModel.loadByUrl(url)
20} 46}
21 47
48function getVideo (res: Response) {
49 return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights || res.locals.videoId
50}
51
52function getVideoWithAttributes (res: Response) {
53 return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights
54}
55
22export { 56export {
23 VideoFetchType, 57 VideoFetchType,
24 VideoFetchByUrlType, 58 VideoFetchByUrlType,
25 fetchVideo, 59 fetchVideo,
60 getVideo,
61 getVideoWithAttributes,
26 fetchVideoByUrl 62 fetchVideoByUrl
27} 63}
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts
index d1229e28f..5443a266b 100644
--- a/server/helpers/webfinger.ts
+++ b/server/helpers/webfinger.ts
@@ -4,6 +4,7 @@ import { ActorModel } from '../models/activitypub/actor'
4import { isTestInstance } from './core-utils' 4import { isTestInstance } from './core-utils'
5import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' 5import { isActivityPubUrlValid } from './custom-validators/activitypub/misc'
6import { WEBSERVER } from '../initializers/constants' 6import { WEBSERVER } from '../initializers/constants'
7import { MActorFull } from '../typings/models'
7 8
8const webfinger = new WebFinger({ 9const webfinger = new WebFinger({
9 webfist_fallback: false, 10 webfist_fallback: false,
@@ -17,7 +18,7 @@ async function loadActorUrlOrGetFromWebfinger (uriArg: string) {
17 const uri = uriArg.startsWith('@') ? uriArg.slice(1) : uriArg 18 const uri = uriArg.startsWith('@') ? uriArg.slice(1) : uriArg
18 19
19 const [ name, host ] = uri.split('@') 20 const [ name, host ] = uri.split('@')
20 let actor: ActorModel 21 let actor: MActorFull
21 22
22 if (!host || host === WEBSERVER.HOST) { 23 if (!host || host === WEBSERVER.HOST) {
23 actor = await ActorModel.loadLocalByName(name) 24 actor = await ActorModel.loadLocalByName(name)
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 3dc178b11..908231a88 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
14 14
15// --------------------------------------------------------------------------- 15// ---------------------------------------------------------------------------
16 16
17const LAST_MIGRATION_VERSION = 420 17const LAST_MIGRATION_VERSION = 425
18 18
19// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
20 20
diff --git a/server/initializers/migrations/0425-nullable-actor-fields.ts b/server/initializers/migrations/0425-nullable-actor-fields.ts
new file mode 100644
index 000000000..4e5f9e6ab
--- /dev/null
+++ b/server/initializers/migrations/0425-nullable-actor-fields.ts
@@ -0,0 +1,26 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize,
7 db: any
8}): Promise<void> {
9 const data = {
10 type: Sequelize.STRING,
11 allowNull: true
12 }
13
14 await utils.queryInterface.changeColumn('actor', 'outboxUrl', data)
15 await utils.queryInterface.changeColumn('actor', 'followersUrl', data)
16 await utils.queryInterface.changeColumn('actor', 'followingUrl', data)
17}
18
19function down (options) {
20 throw new Error('Not implemented.')
21}
22
23export {
24 up,
25 down
26}
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index 9f5d12eb4..13b73077e 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -22,13 +22,27 @@ import { JobQueue } from '../job-queue'
22import { getServerActor } from '../../helpers/utils' 22import { getServerActor } from '../../helpers/utils'
23import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor' 23import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor'
24import { sequelizeTypescript } from '../../initializers/database' 24import { sequelizeTypescript } from '../../initializers/database'
25import {
26 MAccount,
27 MAccountDefault,
28 MActor,
29 MActorAccountChannelId,
30 MActorAccountChannelIdActor,
31 MActorAccountId,
32 MActorDefault,
33 MActorFull,
34 MActorFullActor,
35 MActorId,
36 MChannel,
37 MChannelAccountDefault
38} from '../../typings/models'
25 39
26// Set account keys, this could be long so process after the account creation and do not block the client 40// Set account keys, this could be long so process after the account creation and do not block the client
27function setAsyncActorKeys (actor: ActorModel) { 41function setAsyncActorKeys <T extends MActor> (actor: T) {
28 return createPrivateAndPublicKeys() 42 return createPrivateAndPublicKeys()
29 .then(({ publicKey, privateKey }) => { 43 .then(({ publicKey, privateKey }) => {
30 actor.set('publicKey', publicKey) 44 actor.publicKey = publicKey
31 actor.set('privateKey', privateKey) 45 actor.privateKey = privateKey
32 return actor.save() 46 return actor.save()
33 }) 47 })
34 .catch(err => { 48 .catch(err => {
@@ -37,12 +51,26 @@ function setAsyncActorKeys (actor: ActorModel) {
37 }) 51 })
38} 52}
39 53
54function getOrCreateActorAndServerAndModel (
55 activityActor: string | ActivityPubActor,
56 fetchType: 'all',
57 recurseIfNeeded?: boolean,
58 updateCollections?: boolean
59): Promise<MActorFullActor>
60
61function getOrCreateActorAndServerAndModel (
62 activityActor: string | ActivityPubActor,
63 fetchType?: 'association-ids',
64 recurseIfNeeded?: boolean,
65 updateCollections?: boolean
66): Promise<MActorAccountChannelId>
67
40async function getOrCreateActorAndServerAndModel ( 68async function getOrCreateActorAndServerAndModel (
41 activityActor: string | ActivityPubActor, 69 activityActor: string | ActivityPubActor,
42 fetchType: ActorFetchByUrlType = 'actor-and-association-ids', 70 fetchType: ActorFetchByUrlType = 'association-ids',
43 recurseIfNeeded = true, 71 recurseIfNeeded = true,
44 updateCollections = false 72 updateCollections = false
45) { 73): Promise<MActorFullActor | MActorAccountChannelId> {
46 const actorUrl = getAPId(activityActor) 74 const actorUrl = getAPId(activityActor)
47 let created = false 75 let created = false
48 let accountPlaylistsUrl: string 76 let accountPlaylistsUrl: string
@@ -61,7 +89,7 @@ async function getOrCreateActorAndServerAndModel (
61 89
62 // Create the attributed to actor 90 // Create the attributed to actor
63 // In PeerTube a video channel is owned by an account 91 // In PeerTube a video channel is owned by an account
64 let ownerActor: ActorModel = undefined 92 let ownerActor: MActorFullActor
65 if (recurseIfNeeded === true && result.actor.type === 'Group') { 93 if (recurseIfNeeded === true && result.actor.type === 'Group') {
66 const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person') 94 const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person')
67 if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url) 95 if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url)
@@ -85,8 +113,8 @@ async function getOrCreateActorAndServerAndModel (
85 accountPlaylistsUrl = result.playlists 113 accountPlaylistsUrl = result.playlists
86 } 114 }
87 115
88 if (actor.Account) actor.Account.Actor = actor 116 if (actor.Account) (actor as MActorAccountChannelIdActor).Account.Actor = actor
89 if (actor.VideoChannel) actor.VideoChannel.Actor = actor 117 if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor
90 118
91 const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType) 119 const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType)
92 if (!actorRefreshed) throw new Error('Actor ' + actorRefreshed.url + ' does not exist anymore.') 120 if (!actorRefreshed) throw new Error('Actor ' + actorRefreshed.url + ' does not exist anymore.')
@@ -120,7 +148,7 @@ function buildActorInstance (type: ActivityPubActorType, url: string, preferredU
120 sharedInboxUrl: WEBSERVER.URL + '/inbox', 148 sharedInboxUrl: WEBSERVER.URL + '/inbox',
121 followersUrl: url + '/followers', 149 followersUrl: url + '/followers',
122 followingUrl: url + '/following' 150 followingUrl: url + '/following'
123 }) 151 }) as MActor
124} 152}
125 153
126async function updateActorInstance (actorInstance: ActorModel, attributes: ActivityPubActor) { 154async function updateActorInstance (actorInstance: ActorModel, attributes: ActivityPubActor) {
@@ -140,7 +168,8 @@ async function updateActorInstance (actorInstance: ActorModel, attributes: Activ
140 actorInstance.followingUrl = attributes.following 168 actorInstance.followingUrl = attributes.following
141} 169}
142 170
143async function updateActorAvatarInstance (actor: ActorModel, info: { name: string, onDisk: boolean, fileUrl: string }, t: Transaction) { 171type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string }
172async function updateActorAvatarInstance (actor: MActorDefault, info: AvatarInfo, t: Transaction) {
144 if (info.name !== undefined) { 173 if (info.name !== undefined) {
145 if (actor.avatarId) { 174 if (actor.avatarId) {
146 try { 175 try {
@@ -212,14 +241,16 @@ async function addFetchOutboxJob (actor: Pick<ActorModel, 'id' | 'outboxUrl'>) {
212 return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) 241 return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })
213} 242}
214 243
215async function refreshActorIfNeeded ( 244async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannelId> (
216 actorArg: ActorModel, 245 actorArg: T,
217 fetchedType: ActorFetchByUrlType 246 fetchedType: ActorFetchByUrlType
218): Promise<{ actor: ActorModel, refreshed: boolean }> { 247): Promise<{ actor: T | MActorFull, refreshed: boolean }> {
219 if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false } 248 if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false }
220 249
221 // We need more attributes 250 // We need more attributes
222 const actor = fetchedType === 'all' ? actorArg : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url) 251 const actor = fetchedType === 'all'
252 ? actorArg as MActorFull
253 : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url)
223 254
224 try { 255 try {
225 let actorUrl: string 256 let actorUrl: string
@@ -297,9 +328,9 @@ export {
297 328
298function saveActorAndServerAndModelIfNotExist ( 329function saveActorAndServerAndModelIfNotExist (
299 result: FetchRemoteActorResult, 330 result: FetchRemoteActorResult,
300 ownerActor?: ActorModel, 331 ownerActor?: MActorFullActor,
301 t?: Transaction 332 t?: Transaction
302): Bluebird<ActorModel> | Promise<ActorModel> { 333): Bluebird<MActorFullActor> | Promise<MActorFullActor> {
303 let actor = result.actor 334 let actor = result.actor
304 335
305 if (t !== undefined) return save(t) 336 if (t !== undefined) return save(t)
@@ -336,7 +367,7 @@ function saveActorAndServerAndModelIfNotExist (
336 367
337 // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists 368 // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists
338 // (which could be false in a retried query) 369 // (which could be false in a retried query)
339 const [ actorCreated ] = await ActorModel.findOrCreate({ 370 const [ actorCreated ] = await ActorModel.findOrCreate<MActorFullActor>({
340 defaults: actor.toJSON(), 371 defaults: actor.toJSON(),
341 where: { 372 where: {
342 url: actor.url 373 url: actor.url
@@ -345,12 +376,11 @@ function saveActorAndServerAndModelIfNotExist (
345 }) 376 })
346 377
347 if (actorCreated.type === 'Person' || actorCreated.type === 'Application') { 378 if (actorCreated.type === 'Person' || actorCreated.type === 'Application') {
348 actorCreated.Account = await saveAccount(actorCreated, result, t) 379 actorCreated.Account = await saveAccount(actorCreated, result, t) as MAccountDefault
349 actorCreated.Account.Actor = actorCreated 380 actorCreated.Account.Actor = actorCreated
350 } else if (actorCreated.type === 'Group') { // Video channel 381 } else if (actorCreated.type === 'Group') { // Video channel
351 actorCreated.VideoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t) 382 const channel = await saveVideoChannel(actorCreated, result, ownerActor, t)
352 actorCreated.VideoChannel.Actor = actorCreated 383 actorCreated.VideoChannel = Object.assign(channel, { Actor: actorCreated, Account: ownerActor.Account })
353 actorCreated.VideoChannel.Account = ownerActor.Account
354 } 384 }
355 385
356 actorCreated.Server = server 386 actorCreated.Server = server
@@ -360,7 +390,7 @@ function saveActorAndServerAndModelIfNotExist (
360} 390}
361 391
362type FetchRemoteActorResult = { 392type FetchRemoteActorResult = {
363 actor: ActorModel 393 actor: MActor
364 name: string 394 name: string
365 summary: string 395 summary: string
366 support?: string 396 support?: string
@@ -429,7 +459,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe
429 } 459 }
430} 460}
431 461
432async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) { 462async function saveAccount (actor: MActorId, result: FetchRemoteActorResult, t: Transaction) {
433 const [ accountCreated ] = await AccountModel.findOrCreate({ 463 const [ accountCreated ] = await AccountModel.findOrCreate({
434 defaults: { 464 defaults: {
435 name: result.name, 465 name: result.name,
@@ -442,10 +472,10 @@ async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t
442 transaction: t 472 transaction: t
443 }) 473 })
444 474
445 return accountCreated 475 return accountCreated as MAccount
446} 476}
447 477
448async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) { 478async function saveVideoChannel (actor: MActorId, result: FetchRemoteActorResult, ownerActor: MActorAccountId, t: Transaction) {
449 const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({ 479 const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({
450 defaults: { 480 defaults: {
451 name: result.name, 481 name: result.name,
@@ -460,5 +490,5 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu
460 transaction: t 490 transaction: t
461 }) 491 })
462 492
463 return videoChannelCreated 493 return videoChannelCreated as MChannel
464} 494}
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts
index 0e3d78590..f2ab54cf7 100644
--- a/server/lib/activitypub/audience.ts
+++ b/server/lib/activitypub/audience.ts
@@ -3,11 +3,10 @@ import { 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/activitypub/actor'
5import { VideoModel } from '../../models/video/video' 5import { VideoModel } from '../../models/video/video'
6import { VideoCommentModel } from '../../models/video/video-comment'
7import { VideoShareModel } from '../../models/video/video-share' 6import { VideoShareModel } from '../../models/video/video-share'
8import { ActorModelOnly } from '../../typings/models' 7import { MActorFollowersUrl, MActorLight, MCommentOwner, MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../../typings/models'
9 8
10function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]): ActivityAudience { 9function getRemoteVideoAudience (video: MVideoAccountLight, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience {
11 return { 10 return {
12 to: [ video.VideoChannel.Account.Actor.url ], 11 to: [ video.VideoChannel.Account.Actor.url ],
13 cc: actorsInvolvedInVideo.map(a => a.followersUrl) 12 cc: actorsInvolvedInVideo.map(a => a.followersUrl)
@@ -15,9 +14,9 @@ function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: Actor
15} 14}
16 15
17function getVideoCommentAudience ( 16function getVideoCommentAudience (
18 videoComment: VideoCommentModel, 17 videoComment: MCommentOwnerVideo,
19 threadParentComments: VideoCommentModel[], 18 threadParentComments: MCommentOwner[],
20 actorsInvolvedInVideo: ActorModel[], 19 actorsInvolvedInVideo: MActorFollowersUrl[],
21 isOrigin = false 20 isOrigin = false
22): ActivityAudience { 21): ActivityAudience {
23 const to = [ ACTIVITY_PUB.PUBLIC ] 22 const to = [ ACTIVITY_PUB.PUBLIC ]
@@ -42,26 +41,28 @@ function getVideoCommentAudience (
42 } 41 }
43} 42}
44 43
45function getAudienceFromFollowersOf (actorsInvolvedInObject: ActorModel[]): ActivityAudience { 44function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience {
46 return { 45 return {
47 to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)), 46 to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
48 cc: [] 47 cc: []
49 } 48 }
50} 49}
51 50
52async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) { 51async function getActorsInvolvedInVideo (video: MVideo, t: Transaction) {
53 const actors = await VideoShareModel.loadActorsByShare(video.id, t) 52 const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t)
54 53
55 const videoActor = video.VideoChannel && video.VideoChannel.Account 54 const videoAll = video as VideoModel
56 ? video.VideoChannel.Account.Actor 55
57 : await ActorModel.loadAccountActorByVideoId(video.id, t) 56 const videoActor = videoAll.VideoChannel && videoAll.VideoChannel.Account
57 ? videoAll.VideoChannel.Account.Actor
58 : await ActorModel.loadFromAccountByVideoId(video.id, t)
58 59
59 actors.push(videoActor) 60 actors.push(videoActor)
60 61
61 return actors 62 return actors
62} 63}
63 64
64function getAudience (actorSender: ActorModelOnly, isPublic = true) { 65function getAudience (actorSender: MActorFollowersUrl, isPublic = true) {
65 return buildAudience([ actorSender.followersUrl ], isPublic) 66 return buildAudience([ actorSender.followersUrl ], isPublic)
66} 67}
67 68
diff --git a/server/lib/activitypub/cache-file.ts b/server/lib/activitypub/cache-file.ts
index de5cc54ac..65b2dcb49 100644
--- a/server/lib/activitypub/cache-file.ts
+++ b/server/lib/activitypub/cache-file.ts
@@ -1,10 +1,10 @@
1import { CacheFileObject } from '../../../shared/index' 1import { CacheFileObject } from '../../../shared/index'
2import { VideoModel } from '../../models/video/video'
3import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' 2import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
4import { Transaction } from 'sequelize' 3import { Transaction } from 'sequelize'
5import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 4import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
5import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/typings/models'
6 6
7function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }) { 7function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId) {
8 8
9 if (cacheFileObject.url.mediaType === 'application/x-mpegURL') { 9 if (cacheFileObject.url.mediaType === 'application/x-mpegURL') {
10 const url = cacheFileObject.url 10 const url = cacheFileObject.url
@@ -39,7 +39,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject
39 } 39 }
40} 40}
41 41
42async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) { 42async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
43 const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t) 43 const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t)
44 44
45 if (!redundancyModel) { 45 if (!redundancyModel) {
@@ -49,7 +49,7 @@ async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video:
49 } 49 }
50} 50}
51 51
52function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) { 52function createCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
53 const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor) 53 const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor)
54 54
55 return VideoRedundancyModel.create(attributes, { transaction: t }) 55 return VideoRedundancyModel.create(attributes, { transaction: t })
@@ -57,9 +57,9 @@ function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, b
57 57
58function updateCacheFile ( 58function updateCacheFile (
59 cacheFileObject: CacheFileObject, 59 cacheFileObject: CacheFileObject,
60 redundancyModel: VideoRedundancyModel, 60 redundancyModel: MVideoRedundancy,
61 video: VideoModel, 61 video: MVideoWithAllFiles,
62 byActor: { id?: number }, 62 byActor: MActorId,
63 t: Transaction 63 t: Transaction
64) { 64) {
65 if (redundancyModel.actorId !== byActor.id) { 65 if (redundancyModel.actorId !== byActor.id) {
diff --git a/server/lib/activitypub/playlist.ts b/server/lib/activitypub/playlist.ts
index c2e2a3283..c52b715ef 100644
--- a/server/lib/activitypub/playlist.ts
+++ b/server/lib/activitypub/playlist.ts
@@ -1,7 +1,6 @@
1import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' 1import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
2import { crawlCollectionPage } from './crawl' 2import { crawlCollectionPage } from './crawl'
3import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' 3import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
4import { AccountModel } from '../../models/account/account'
5import { isArray } from '../../helpers/custom-validators/misc' 4import { isArray } from '../../helpers/custom-validators/misc'
6import { getOrCreateActorAndServerAndModel } from './actor' 5import { getOrCreateActorAndServerAndModel } from './actor'
7import { logger } from '../../helpers/logger' 6import { logger } from '../../helpers/logger'
@@ -13,14 +12,14 @@ import { PlaylistElementObject } from '../../../shared/models/activitypub/object
13import { getOrCreateVideoAndAccountAndChannel } from './videos' 12import { getOrCreateVideoAndAccountAndChannel } from './videos'
14import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist' 13import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist'
15import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' 14import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element'
16import { VideoModel } from '../../models/video/video'
17import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' 15import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
18import { sequelizeTypescript } from '../../initializers/database' 16import { sequelizeTypescript } from '../../initializers/database'
19import { createPlaylistMiniatureFromUrl } from '../thumbnail' 17import { createPlaylistMiniatureFromUrl } from '../thumbnail'
20import { FilteredModelAttributes } from '../../typings/sequelize' 18import { FilteredModelAttributes } from '../../typings/sequelize'
21import { AccountModelId } from '../../typings/models' 19import { MAccountDefault, MAccountId, MVideoId } from '../../typings/models'
20import { MVideoPlaylist, MVideoPlaylistId, MVideoPlaylistOwner } from '../../typings/models/video/video-playlist'
22 21
23function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) { 22function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) {
24 const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED 23 const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED
25 24
26 return { 25 return {
@@ -36,7 +35,7 @@ function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount
36 } 35 }
37} 36}
38 37
39function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: VideoPlaylistModel, video: VideoModel) { 38function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: MVideoPlaylistId, video: MVideoId) {
40 return { 39 return {
41 position: elementObject.position, 40 position: elementObject.position,
42 url: elementObject.id, 41 url: elementObject.id,
@@ -47,7 +46,7 @@ function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObje
47 } 46 }
48} 47}
49 48
50async function createAccountPlaylists (playlistUrls: string[], account: AccountModel) { 49async function createAccountPlaylists (playlistUrls: string[], account: MAccountDefault) {
51 await Bluebird.map(playlistUrls, async playlistUrl => { 50 await Bluebird.map(playlistUrls, async playlistUrl => {
52 try { 51 try {
53 const exists = await VideoPlaylistModel.doesPlaylistExist(playlistUrl) 52 const exists = await VideoPlaylistModel.doesPlaylistExist(playlistUrl)
@@ -75,7 +74,7 @@ async function createAccountPlaylists (playlistUrls: string[], account: AccountM
75 }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) 74 }, { concurrency: CRAWL_REQUEST_CONCURRENCY })
76} 75}
77 76
78async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) { 77async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) {
79 const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to) 78 const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to)
80 79
81 if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) { 80 if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) {
@@ -88,7 +87,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
88 } 87 }
89 } 88 }
90 89
91 const [ playlist ] = await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true }) 90 const [ playlist ] = await VideoPlaylistModel.upsert<MVideoPlaylist>(playlistAttributes, { returning: true })
92 91
93 let accItems: string[] = [] 92 let accItems: string[] = []
94 await crawlCollectionPage<string>(playlistObject.id, items => { 93 await crawlCollectionPage<string>(playlistObject.id, items => {
@@ -114,7 +113,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
114 return resetVideoPlaylistElements(accItems, refreshedPlaylist) 113 return resetVideoPlaylistElements(accItems, refreshedPlaylist)
115} 114}
116 115
117async function refreshVideoPlaylistIfNeeded (videoPlaylist: VideoPlaylistModel): Promise<VideoPlaylistModel> { 116async function refreshVideoPlaylistIfNeeded (videoPlaylist: MVideoPlaylistOwner): Promise<MVideoPlaylistOwner> {
118 if (!videoPlaylist.isOutdated()) return videoPlaylist 117 if (!videoPlaylist.isOutdated()) return videoPlaylist
119 118
120 try { 119 try {
@@ -157,7 +156,7 @@ export {
157 156
158// --------------------------------------------------------------------------- 157// ---------------------------------------------------------------------------
159 158
160async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) { 159async function resetVideoPlaylistElements (elementUrls: string[], playlist: MVideoPlaylist) {
161 const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = [] 160 const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = []
162 161
163 await Bluebird.map(elementUrls, async elementUrl => { 162 await Bluebird.map(elementUrls, async elementUrl => {
diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts
index cf27e6c32..86f7c764d 100644
--- a/server/lib/activitypub/process/process-accept.ts
+++ b/server/lib/activitypub/process/process-accept.ts
@@ -1,9 +1,8 @@
1import { ActivityAccept } from '../../../../shared/models/activitypub' 1import { ActivityAccept } from '../../../../shared/models/activitypub'
2import { ActorModel } from '../../../models/activitypub/actor'
3import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 2import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
4import { addFetchOutboxJob } from '../actor' 3import { addFetchOutboxJob } from '../actor'
5import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 4import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
6import { SignatureActorModel } from '../../../typings/models' 5import { MActorDefault, MActorSignature } from '../../../typings/models'
7 6
8async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) { 7async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) {
9 const { byActor: targetActor, inboxActor } = options 8 const { byActor: targetActor, inboxActor } = options
@@ -20,7 +19,7 @@ export {
20 19
21// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
22 21
23async function processAccept (actor: ActorModel, targetActor: SignatureActorModel) { 22async function processAccept (actor: MActorDefault, targetActor: MActorSignature) {
24 const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id) 23 const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id)
25 if (!follow) throw new Error('Cannot find associated follow.') 24 if (!follow) throw new Error('Cannot find associated follow.')
26 25
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts
index b3cdc4441..7e22125d5 100644
--- a/server/lib/activitypub/process/process-announce.ts
+++ b/server/lib/activitypub/process/process-announce.ts
@@ -5,10 +5,9 @@ import { VideoShareModel } from '../../../models/video/video-share'
5import { forwardVideoRelatedActivity } from '../send/utils' 5import { forwardVideoRelatedActivity } from '../send/utils'
6import { getOrCreateVideoAndAccountAndChannel } from '../videos' 6import { getOrCreateVideoAndAccountAndChannel } from '../videos'
7import { Notifier } from '../../notifier' 7import { Notifier } from '../../notifier'
8import { VideoModel } from '../../../models/video/video'
9import { logger } from '../../../helpers/logger' 8import { logger } from '../../../helpers/logger'
10import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 9import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
11import { SignatureActorModel } from '../../../typings/models' 10import { MActorSignature, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models'
12 11
13async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) { 12async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) {
14 const { activity, byActor: actorAnnouncer } = options 13 const { activity, byActor: actorAnnouncer } = options
@@ -26,10 +25,10 @@ export {
26 25
27// --------------------------------------------------------------------------- 26// ---------------------------------------------------------------------------
28 27
29async function processVideoShare (actorAnnouncer: SignatureActorModel, activity: ActivityAnnounce, notify: boolean) { 28async function processVideoShare (actorAnnouncer: MActorSignature, activity: ActivityAnnounce, notify: boolean) {
30 const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id 29 const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id
31 30
32 let video: VideoModel 31 let video: MVideoAccountLightBlacklistAllFiles
33 let videoCreated: boolean 32 let videoCreated: boolean
34 33
35 try { 34 try {
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index 6815c6997..bee853721 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -10,10 +10,8 @@ import { createOrUpdateCacheFile } from '../cache-file'
10import { Notifier } from '../../notifier' 10import { Notifier } from '../../notifier'
11import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' 11import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
12import { createOrUpdateVideoPlaylist } from '../playlist' 12import { createOrUpdateVideoPlaylist } from '../playlist'
13import { VideoModel } from '../../../models/video/video'
14import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 13import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
15import { VideoCommentModel } from '../../../models/video/video-comment' 14import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models'
16import { SignatureActorModel } from '../../../typings/models'
17 15
18async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) { 16async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) {
19 const { activity, byActor } = options 17 const { activity, byActor } = options
@@ -61,7 +59,7 @@ async function processCreateVideo (activity: ActivityCreate, notify: boolean) {
61 return video 59 return video
62} 60}
63 61
64async function processCreateCacheFile (activity: ActivityCreate, byActor: SignatureActorModel) { 62async function processCreateCacheFile (activity: ActivityCreate, byActor: MActorSignature) {
65 const cacheFile = activity.object as CacheFileObject 63 const cacheFile = activity.object as CacheFileObject
66 64
67 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object }) 65 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object })
@@ -77,15 +75,15 @@ async function processCreateCacheFile (activity: ActivityCreate, byActor: Signat
77 } 75 }
78} 76}
79 77
80async function processCreateVideoComment (activity: ActivityCreate, byActor: SignatureActorModel, notify: boolean) { 78async function processCreateVideoComment (activity: ActivityCreate, byActor: MActorSignature, notify: boolean) {
81 const commentObject = activity.object as VideoCommentObject 79 const commentObject = activity.object as VideoCommentObject
82 const byAccount = byActor.Account 80 const byAccount = byActor.Account
83 81
84 if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) 82 if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
85 83
86 let video: VideoModel 84 let video: MVideoAccountLightBlacklistAllFiles
87 let created: boolean 85 let created: boolean
88 let comment: VideoCommentModel 86 let comment: MCommentOwnerVideo
89 try { 87 try {
90 const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false }) 88 const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false })
91 video = resolveThreadResult.video 89 video = resolveThreadResult.video
@@ -110,7 +108,7 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: Sig
110 if (created && notify) Notifier.Instance.notifyOnNewComment(comment) 108 if (created && notify) Notifier.Instance.notifyOnNewComment(comment)
111} 109}
112 110
113async function processCreatePlaylist (activity: ActivityCreate, byActor: SignatureActorModel) { 111async function processCreatePlaylist (activity: ActivityCreate, byActor: MActorSignature) {
114 const playlistObject = activity.object as PlaylistObject 112 const playlistObject = activity.object as PlaylistObject
115 const byAccount = byActor.Account 113 const byAccount = byActor.Account
116 114
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts
index 344d14322..79d0e0d79 100644
--- a/server/lib/activitypub/process/process-delete.ts
+++ b/server/lib/activitypub/process/process-delete.ts
@@ -2,15 +2,13 @@ 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' 4import { sequelizeTypescript } from '../../../initializers'
5import { AccountModel } from '../../../models/account/account'
6import { ActorModel } from '../../../models/activitypub/actor' 5import { ActorModel } from '../../../models/activitypub/actor'
7import { VideoModel } from '../../../models/video/video' 6import { VideoModel } from '../../../models/video/video'
8import { VideoChannelModel } from '../../../models/video/video-channel'
9import { VideoCommentModel } from '../../../models/video/video-comment' 7import { VideoCommentModel } from '../../../models/video/video-comment'
10import { forwardVideoRelatedActivity } from '../send/utils' 8import { forwardVideoRelatedActivity } from '../send/utils'
11import { VideoPlaylistModel } from '../../../models/video/video-playlist' 9import { VideoPlaylistModel } from '../../../models/video/video-playlist'
12import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 10import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
13import { SignatureActorModel } from '../../../typings/models' 11import { MAccountActor, MActor, MActorSignature, MChannelActor, MChannelActorAccountActor } from '../../../typings/models'
14 12
15async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) { 13async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) {
16 const { activity, byActor } = options 14 const { activity, byActor } = options
@@ -24,13 +22,17 @@ async function processDeleteActivity (options: APProcessorOptions<ActivityDelete
24 if (byActorFull.type === 'Person') { 22 if (byActorFull.type === 'Person') {
25 if (!byActorFull.Account) throw new Error('Actor ' + byActorFull.url + ' is a person but we cannot find it in database.') 23 if (!byActorFull.Account) throw new Error('Actor ' + byActorFull.url + ' is a person but we cannot find it in database.')
26 24
27 byActorFull.Account.Actor = await byActorFull.Account.$get('Actor') as ActorModel 25 const accountToDelete = byActorFull.Account as MAccountActor
28 return retryTransactionWrapper(processDeleteAccount, byActorFull.Account) 26 accountToDelete.Actor = byActorFull
27
28 return retryTransactionWrapper(processDeleteAccount, accountToDelete)
29 } else if (byActorFull.type === 'Group') { 29 } else if (byActorFull.type === 'Group') {
30 if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.') 30 if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.')
31 31
32 byActorFull.VideoChannel.Actor = await byActorFull.VideoChannel.$get('Actor') as ActorModel 32 const channelToDelete = byActorFull.VideoChannel as MChannelActorAccountActor
33 return retryTransactionWrapper(processDeleteVideoChannel, byActorFull.VideoChannel) 33 channelToDelete.Actor = byActorFull
34
35 return retryTransactionWrapper(processDeleteVideoChannel, channelToDelete)
34 } 36 }
35 } 37 }
36 38
@@ -70,7 +72,7 @@ export {
70 72
71// --------------------------------------------------------------------------- 73// ---------------------------------------------------------------------------
72 74
73async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) { 75async function processDeleteVideo (actor: MActor, videoToDelete: VideoModel) {
74 logger.debug('Removing remote video "%s".', videoToDelete.uuid) 76 logger.debug('Removing remote video "%s".', videoToDelete.uuid)
75 77
76 await sequelizeTypescript.transaction(async t => { 78 await sequelizeTypescript.transaction(async t => {
@@ -84,7 +86,7 @@ async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel)
84 logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) 86 logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
85} 87}
86 88
87async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: VideoPlaylistModel) { 89async function processDeleteVideoPlaylist (actor: MActor, playlistToDelete: VideoPlaylistModel) {
88 logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid) 90 logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid)
89 91
90 await sequelizeTypescript.transaction(async t => { 92 await sequelizeTypescript.transaction(async t => {
@@ -98,7 +100,7 @@ async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete:
98 logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid) 100 logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid)
99} 101}
100 102
101async function processDeleteAccount (accountToRemove: AccountModel) { 103async function processDeleteAccount (accountToRemove: MAccountActor) {
102 logger.debug('Removing remote account "%s".', accountToRemove.Actor.url) 104 logger.debug('Removing remote account "%s".', accountToRemove.Actor.url)
103 105
104 await sequelizeTypescript.transaction(async t => { 106 await sequelizeTypescript.transaction(async t => {
@@ -108,7 +110,7 @@ async function processDeleteAccount (accountToRemove: AccountModel) {
108 logger.info('Remote account %s removed.', accountToRemove.Actor.url) 110 logger.info('Remote account %s removed.', accountToRemove.Actor.url)
109} 111}
110 112
111async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) { 113async function processDeleteVideoChannel (videoChannelToRemove: MChannelActor) {
112 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url) 114 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url)
113 115
114 await sequelizeTypescript.transaction(async t => { 116 await sequelizeTypescript.transaction(async t => {
@@ -118,7 +120,7 @@ async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelMode
118 logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url) 120 logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url)
119} 121}
120 122
121function processDeleteVideoComment (byActor: SignatureActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) { 123function processDeleteVideoComment (byActor: MActorSignature, videoComment: VideoCommentModel, activity: ActivityDelete) {
122 logger.debug('Removing remote video comment "%s".', videoComment.url) 124 logger.debug('Removing remote video comment "%s".', videoComment.url)
123 125
124 return sequelizeTypescript.transaction(async t => { 126 return sequelizeTypescript.transaction(async t => {
diff --git a/server/lib/activitypub/process/process-dislike.ts b/server/lib/activitypub/process/process-dislike.ts
index 727fcfee0..debd8a67c 100644
--- a/server/lib/activitypub/process/process-dislike.ts
+++ b/server/lib/activitypub/process/process-dislike.ts
@@ -7,7 +7,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
7import { forwardVideoRelatedActivity } from '../send/utils' 7import { forwardVideoRelatedActivity } from '../send/utils'
8import { getVideoDislikeActivityPubUrl } from '../url' 8import { getVideoDislikeActivityPubUrl } from '../url'
9import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 9import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
10import { SignatureActorModel } from '../../../typings/models' 10import { MActorSignature } from '../../../typings/models'
11 11
12async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) { 12async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) {
13 const { activity, byActor } = options 13 const { activity, byActor } = options
@@ -22,7 +22,7 @@ export {
22 22
23// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
24 24
25async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: SignatureActorModel) { 25async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: MActorSignature) {
26 const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).object 26 const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).object
27 const byAccount = byActor.Account 27 const byAccount = byActor.Account
28 28
diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts
index 1f8a80c14..e6e9084de 100644
--- a/server/lib/activitypub/process/process-flag.ts
+++ b/server/lib/activitypub/process/process-flag.ts
@@ -8,7 +8,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
8import { Notifier } from '../../notifier' 8import { Notifier } from '../../notifier'
9import { getAPId } from '../../../helpers/activitypub' 9import { getAPId } from '../../../helpers/activitypub'
10import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 10import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
11import { SignatureActorModel } from '../../../typings/models' 11import { MActorSignature, MVideoAbuseVideo } from '../../../typings/models'
12 12
13async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { 13async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) {
14 const { activity, byActor } = options 14 const { activity, byActor } = options
@@ -23,31 +23,39 @@ export {
23 23
24// --------------------------------------------------------------------------- 24// ---------------------------------------------------------------------------
25 25
26async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: SignatureActorModel) { 26async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) {
27 const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) 27 const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject)
28 28
29 logger.debug('Reporting remote abuse for video %s.', getAPId(flag.object))
30
31 const account = byActor.Account 29 const account = byActor.Account
32 if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url) 30 if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url)
33 31
34 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: flag.object }) 32 const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ]
35 33
36 const videoAbuse = await sequelizeTypescript.transaction(async t => { 34 for (const object of objects) {
37 const videoAbuseData = { 35 try {
38 reporterAccountId: account.id, 36 logger.debug('Reporting remote abuse for video %s.', getAPId(object))
39 reason: flag.content, 37
40 videoId: video.id, 38 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object })
41 state: VideoAbuseState.PENDING
42 }
43 39
44 const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) 40 const videoAbuse = await sequelizeTypescript.transaction(async t => {
45 videoAbuseInstance.Video = video 41 const videoAbuseData = {
42 reporterAccountId: account.id,
43 reason: flag.content,
44 videoId: video.id,
45 state: VideoAbuseState.PENDING
46 }
46 47
47 logger.info('Remote abuse for video uuid %s created', flag.object) 48 const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo
49 videoAbuseInstance.Video = video
48 50
49 return videoAbuseInstance 51 logger.info('Remote abuse for video uuid %s created', flag.object)
50 })
51 52
52 Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) 53 return videoAbuseInstance
54 })
55
56 Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse)
57 } catch (err) {
58 logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err })
59 }
60 }
53} 61}
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
index 240aa5799..bc5660395 100644
--- a/server/lib/activitypub/process/process-follow.ts
+++ b/server/lib/activitypub/process/process-follow.ts
@@ -10,8 +10,7 @@ import { getAPId } from '../../../helpers/activitypub'
10import { getServerActor } from '../../../helpers/utils' 10import { getServerActor } from '../../../helpers/utils'
11import { CONFIG } from '../../../initializers/config' 11import { CONFIG } from '../../../initializers/config'
12import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 12import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
13import { SignatureActorModel } from '../../../typings/models' 13import { MAccount, MActorFollowActors, MActorFollowFull, MActorSignature } from '../../../typings/models'
14import { ActorFollowModelLight } from '../../../typings/models/actor-follow'
15 14
16async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) { 15async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) {
17 const { activity, byActor } = options 16 const { activity, byActor } = options
@@ -28,7 +27,7 @@ export {
28 27
29// --------------------------------------------------------------------------- 28// ---------------------------------------------------------------------------
30 29
31async function processFollow (byActor: SignatureActorModel, targetActorURL: string) { 30async function processFollow (byActor: MActorSignature, targetActorURL: string) {
32 const { actorFollow, created, isFollowingInstance } = await sequelizeTypescript.transaction(async t => { 31 const { actorFollow, created, isFollowingInstance } = await sequelizeTypescript.transaction(async t => {
33 const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t) 32 const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t)
34 33
@@ -43,10 +42,10 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri
43 42
44 await sendReject(byActor, targetActor) 43 await sendReject(byActor, targetActor)
45 44
46 return { actorFollow: undefined } 45 return { actorFollow: undefined as MActorFollowActors }
47 } 46 }
48 47
49 const [ actorFollow, created ] = await ActorFollowModel.findOrCreate({ 48 const [ actorFollow, created ] = await ActorFollowModel.findOrCreate<MActorFollowActors>({
50 where: { 49 where: {
51 actorId: byActor.id, 50 actorId: byActor.id,
52 targetActorId: targetActor.id 51 targetActorId: targetActor.id
@@ -57,7 +56,7 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri
57 state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted' 56 state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted'
58 }, 57 },
59 transaction: t 58 transaction: t
60 }) as [ ActorFollowModelLight, boolean ] 59 })
61 60
62 if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) { 61 if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) {
63 actorFollow.state = 'accepted' 62 actorFollow.state = 'accepted'
@@ -77,8 +76,14 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri
77 if (!actorFollow) return 76 if (!actorFollow) return
78 77
79 if (created) { 78 if (created) {
80 if (isFollowingInstance) Notifier.Instance.notifyOfNewInstanceFollow(actorFollow) 79 if (isFollowingInstance) {
81 else Notifier.Instance.notifyOfNewUserFollow(actorFollow) 80 Notifier.Instance.notifyOfNewInstanceFollow(actorFollow)
81 } else {
82 const actorFollowFull = actorFollow as MActorFollowFull
83 actorFollowFull.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as MAccount
84
85 Notifier.Instance.notifyOfNewUserFollow(actorFollowFull)
86 }
82 } 87 }
83 88
84 logger.info('Actor %s is followed by actor %s.', targetActorURL, byActor.url) 89 logger.info('Actor %s is followed by actor %s.', targetActorURL, byActor.url)
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts
index cf559af72..62be0de42 100644
--- a/server/lib/activitypub/process/process-like.ts
+++ b/server/lib/activitypub/process/process-like.ts
@@ -7,7 +7,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
7import { getVideoLikeActivityPubUrl } from '../url' 7import { getVideoLikeActivityPubUrl } from '../url'
8import { getAPId } from '../../../helpers/activitypub' 8import { getAPId } from '../../../helpers/activitypub'
9import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 9import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
10import { SignatureActorModel } from '../../../typings/models' 10import { MActorSignature } from '../../../typings/models'
11 11
12async function processLikeActivity (options: APProcessorOptions<ActivityLike>) { 12async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
13 const { activity, byActor } = options 13 const { activity, byActor } = options
@@ -22,7 +22,7 @@ export {
22 22
23// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
24 24
25async function processLikeVideo (byActor: SignatureActorModel, activity: ActivityLike) { 25async function processLikeVideo (byActor: MActorSignature, activity: ActivityLike) {
26 const videoUrl = getAPId(activity.object) 26 const videoUrl = getAPId(activity.object)
27 27
28 const byAccount = byActor.Account 28 const byAccount = byActor.Account
diff --git a/server/lib/activitypub/process/process-reject.ts b/server/lib/activitypub/process/process-reject.ts
index 22e311ceb..00e9afa10 100644
--- a/server/lib/activitypub/process/process-reject.ts
+++ b/server/lib/activitypub/process/process-reject.ts
@@ -2,7 +2,7 @@ import { ActivityReject } from '../../../../shared/models/activitypub/activity'
2import { sequelizeTypescript } from '../../../initializers' 2import { sequelizeTypescript } from '../../../initializers'
3import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 3import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
4import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 4import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
5import { ActorModelOnly } from '../../../typings/models' 5import { MActor } from '../../../typings/models'
6 6
7async function processRejectActivity (options: APProcessorOptions<ActivityReject>) { 7async function processRejectActivity (options: APProcessorOptions<ActivityReject>) {
8 const { byActor: targetActor, inboxActor } = options 8 const { byActor: targetActor, inboxActor } = options
@@ -19,7 +19,7 @@ export {
19 19
20// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
21 21
22async function processReject (follower: ActorModelOnly, targetActor: ActorModelOnly) { 22async function processReject (follower: MActor, targetActor: MActor) {
23 return sequelizeTypescript.transaction(async t => { 23 return sequelizeTypescript.transaction(async t => {
24 const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, targetActor.id, t) 24 const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, targetActor.id, t)
25 25
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts
index c37ee38bb..10643b2e9 100644
--- a/server/lib/activitypub/process/process-undo.ts
+++ b/server/lib/activitypub/process/process-undo.ts
@@ -11,7 +11,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
11import { VideoShareModel } from '../../../models/video/video-share' 11import { VideoShareModel } from '../../../models/video/video-share'
12import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' 12import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
13import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 13import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
14import { SignatureActorModel } from '../../../typings/models' 14import { MActorSignature } from '../../../typings/models'
15 15
16async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) { 16async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) {
17 const { activity, byActor } = options 17 const { activity, byActor } = options
@@ -54,7 +54,7 @@ export {
54 54
55// --------------------------------------------------------------------------- 55// ---------------------------------------------------------------------------
56 56
57async function processUndoLike (byActor: SignatureActorModel, activity: ActivityUndo) { 57async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo) {
58 const likeActivity = activity.object as ActivityLike 58 const likeActivity = activity.object as ActivityLike
59 59
60 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object }) 60 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object })
@@ -77,7 +77,7 @@ async function processUndoLike (byActor: SignatureActorModel, activity: Activity
77 }) 77 })
78} 78}
79 79
80async function processUndoDislike (byActor: SignatureActorModel, activity: ActivityUndo) { 80async function processUndoDislike (byActor: MActorSignature, activity: ActivityUndo) {
81 const dislike = activity.object.type === 'Dislike' 81 const dislike = activity.object.type === 'Dislike'
82 ? activity.object 82 ? activity.object
83 : activity.object.object as DislikeObject 83 : activity.object.object as DislikeObject
@@ -102,7 +102,7 @@ async function processUndoDislike (byActor: SignatureActorModel, activity: Activ
102 }) 102 })
103} 103}
104 104
105async function processUndoCacheFile (byActor: SignatureActorModel, activity: ActivityUndo) { 105async function processUndoCacheFile (byActor: MActorSignature, activity: ActivityUndo) {
106 const cacheFileObject = activity.object.object as CacheFileObject 106 const cacheFileObject = activity.object.object as CacheFileObject
107 107
108 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object }) 108 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object })
@@ -127,7 +127,7 @@ async function processUndoCacheFile (byActor: SignatureActorModel, activity: Act
127 }) 127 })
128} 128}
129 129
130function processUndoFollow (follower: SignatureActorModel, followActivity: ActivityFollow) { 130function processUndoFollow (follower: MActorSignature, followActivity: ActivityFollow) {
131 return sequelizeTypescript.transaction(async t => { 131 return sequelizeTypescript.transaction(async t => {
132 const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t) 132 const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t)
133 const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t) 133 const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t)
@@ -140,7 +140,7 @@ function processUndoFollow (follower: SignatureActorModel, followActivity: Activ
140 }) 140 })
141} 141}
142 142
143function processUndoAnnounce (byActor: SignatureActorModel, announceActivity: ActivityAnnounce) { 143function processUndoAnnounce (byActor: MActorSignature, announceActivity: ActivityAnnounce) {
144 return sequelizeTypescript.transaction(async t => { 144 return sequelizeTypescript.transaction(async t => {
145 const share = await VideoShareModel.loadByUrl(announceActivity.id, t) 145 const share = await VideoShareModel.loadByUrl(announceActivity.id, t)
146 if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`) 146 if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`)
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index 414f9e375..a47d605d8 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -15,7 +15,7 @@ import { forwardVideoRelatedActivity } from '../send/utils'
15import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' 15import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
16import { createOrUpdateVideoPlaylist } from '../playlist' 16import { createOrUpdateVideoPlaylist } from '../playlist'
17import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 17import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
18import { SignatureActorModel } from '../../../typings/models' 18import { MActorSignature, MAccountIdActor } from '../../../typings/models'
19 19
20async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { 20async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) {
21 const { activity, byActor } = options 21 const { activity, byActor } = options
@@ -53,7 +53,7 @@ export {
53 53
54// --------------------------------------------------------------------------- 54// ---------------------------------------------------------------------------
55 55
56async function processUpdateVideo (actor: SignatureActorModel, activity: ActivityUpdate) { 56async function processUpdateVideo (actor: MActorSignature, activity: ActivityUpdate) {
57 const videoObject = activity.object as VideoTorrentObject 57 const videoObject = activity.object as VideoTorrentObject
58 58
59 if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) { 59 if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) {
@@ -61,20 +61,23 @@ async function processUpdateVideo (actor: SignatureActorModel, activity: Activit
61 return undefined 61 return undefined
62 } 62 }
63 63
64 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false }) 64 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false, fetchType: 'all' })
65 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) 65 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
66 66
67 const account = actor.Account as MAccountIdActor
68 account.Actor = actor
69
67 const updateOptions = { 70 const updateOptions = {
68 video, 71 video,
69 videoObject, 72 videoObject,
70 account: actor.Account, 73 account,
71 channel: channelActor.VideoChannel, 74 channel: channelActor.VideoChannel,
72 overrideTo: activity.to 75 overrideTo: activity.to
73 } 76 }
74 return updateVideoFromAP(updateOptions) 77 return updateVideoFromAP(updateOptions)
75} 78}
76 79
77async function processUpdateCacheFile (byActor: SignatureActorModel, activity: ActivityUpdate) { 80async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) {
78 const cacheFileObject = activity.object as CacheFileObject 81 const cacheFileObject = activity.object as CacheFileObject
79 82
80 if (!isCacheFileObjectValid(cacheFileObject)) { 83 if (!isCacheFileObjectValid(cacheFileObject)) {
@@ -150,7 +153,7 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate)
150 } 153 }
151} 154}
152 155
153async function processUpdatePlaylist (byActor: SignatureActorModel, activity: ActivityUpdate) { 156async function processUpdatePlaylist (byActor: MActorSignature, activity: ActivityUpdate) {
154 const playlistObject = activity.object as PlaylistObject 157 const playlistObject = activity.object as PlaylistObject
155 const byAccount = byActor.Account 158 const byAccount = byActor.Account
156 159
diff --git a/server/lib/activitypub/process/process-view.ts b/server/lib/activitypub/process/process-view.ts
index e4997b828..df29ee968 100644
--- a/server/lib/activitypub/process/process-view.ts
+++ b/server/lib/activitypub/process/process-view.ts
@@ -3,7 +3,7 @@ import { forwardVideoRelatedActivity } from '../send/utils'
3import { Redis } from '../../redis' 3import { Redis } from '../../redis'
4import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub' 4import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub'
5import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 5import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
6import { SignatureActorModel } from '../../../typings/models' 6import { MActorSignature } from '../../../typings/models'
7 7
8async function processViewActivity (options: APProcessorOptions<ActivityCreate | ActivityView>) { 8async function processViewActivity (options: APProcessorOptions<ActivityCreate | ActivityView>) {
9 const { activity, byActor } = options 9 const { activity, byActor } = options
@@ -18,11 +18,11 @@ export {
18 18
19// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
20 20
21async function processCreateView (activity: ActivityView | ActivityCreate, byActor: SignatureActorModel) { 21async function processCreateView (activity: ActivityView | ActivityCreate, byActor: MActorSignature) {
22 const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object 22 const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object
23 23
24 const options = { 24 const options = {
25 videoObject: videoObject, 25 videoObject,
26 fetchType: 'only-video' as 'only-video' 26 fetchType: 'only-video' as 'only-video'
27 } 27 }
28 const { video } = await getOrCreateVideoAndAccountAndChannel(options) 28 const { video } = await getOrCreateVideoAndAccountAndChannel(options)
diff --git a/server/lib/activitypub/process/process.ts b/server/lib/activitypub/process/process.ts
index d108fe321..c602bf218 100644
--- a/server/lib/activitypub/process/process.ts
+++ b/server/lib/activitypub/process/process.ts
@@ -1,7 +1,6 @@
1import { Activity, ActivityType } from '../../../../shared/models/activitypub' 1import { Activity, ActivityType } from '../../../../shared/models/activitypub'
2import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub' 2import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { ActorModel } from '../../../models/activitypub/actor'
5import { processAcceptActivity } from './process-accept' 4import { processAcceptActivity } from './process-accept'
6import { processAnnounceActivity } from './process-announce' 5import { processAnnounceActivity } from './process-announce'
7import { processCreateActivity } from './process-create' 6import { processCreateActivity } from './process-create'
@@ -16,7 +15,7 @@ import { processDislikeActivity } from './process-dislike'
16import { processFlagActivity } from './process-flag' 15import { processFlagActivity } from './process-flag'
17import { processViewActivity } from './process-view' 16import { processViewActivity } from './process-view'
18import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 17import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
19import { SignatureActorModel } from '../../../typings/models' 18import { MActorDefault, MActorSignature } from '../../../typings/models'
20 19
21const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = { 20const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = {
22 Create: processCreateActivity, 21 Create: processCreateActivity,
@@ -36,15 +35,15 @@ const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Act
36async function processActivities ( 35async function processActivities (
37 activities: Activity[], 36 activities: Activity[],
38 options: { 37 options: {
39 signatureActor?: SignatureActorModel 38 signatureActor?: MActorSignature
40 inboxActor?: ActorModel 39 inboxActor?: MActorDefault
41 outboxUrl?: string 40 outboxUrl?: string
42 fromFetch?: boolean 41 fromFetch?: boolean
43 } = {} 42 } = {}
44) { 43) {
45 const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options 44 const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options
46 45
47 const actorsCache: { [ url: string ]: SignatureActorModel } = {} 46 const actorsCache: { [ url: string ]: MActorSignature } = {}
48 47
49 for (const activity of activities) { 48 for (const activity of activities) {
50 if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) { 49 if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) {
@@ -75,7 +74,7 @@ async function processActivities (
75 } 74 }
76 75
77 try { 76 try {
78 await activityProcessor({ activity, byActor, inboxActor: inboxActor, fromFetch }) 77 await activityProcessor({ activity, byActor, inboxActor, fromFetch })
79 } catch (err) { 78 } catch (err) {
80 logger.warn('Cannot process activity %s.', activity.type, { err }) 79 logger.warn('Cannot process activity %s.', activity.type, { err })
81 } 80 }
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts
index 813c42e15..9f0225b64 100644
--- a/server/lib/activitypub/send/send-accept.ts
+++ b/server/lib/activitypub/send/send-accept.ts
@@ -3,10 +3,9 @@ import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from
3import { unicastTo } from './utils' 3import { unicastTo } from './utils'
4import { buildFollowActivity } from './send-follow' 4import { buildFollowActivity } from './send-follow'
5import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6import { ActorFollowModelLight } from '../../../typings/models/actor-follow' 6import { MActor, MActorFollowActors } from '../../../typings/models'
7import { ActorModelOnly } from '../../../typings/models'
8 7
9async function sendAccept (actorFollow: ActorFollowModelLight) { 8async function sendAccept (actorFollow: MActorFollowActors) {
10 const follower = actorFollow.ActorFollower 9 const follower = actorFollow.ActorFollower
11 const me = actorFollow.ActorFollowing 10 const me = actorFollow.ActorFollowing
12 11
@@ -34,7 +33,7 @@ export {
34 33
35// --------------------------------------------------------------------------- 34// ---------------------------------------------------------------------------
36 35
37function buildAcceptActivity (url: string, byActor: ActorModelOnly, followActivityData: ActivityFollow): ActivityAccept { 36function buildAcceptActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityAccept {
38 return { 37 return {
39 type: 'Accept', 38 type: 'Accept',
40 id: url, 39 id: url,
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
index 7fe4ca180..a0f33852c 100644
--- a/server/lib/activitypub/send/send-announce.ts
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -1,16 +1,15 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAnnounce, ActivityAudience } from '../../../../shared/models/activitypub' 2import { ActivityAnnounce, ActivityAudience } from '../../../../shared/models/activitypub'
3import { VideoModel } from '../../../models/video/video'
4import { broadcastToFollowers } from './utils' 3import { broadcastToFollowers } from './utils'
5import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf } from '../audience' 4import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf } from '../audience'
6import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
7import { ActorModelOnly } from '../../../typings/models' 6import { MActorLight, MVideo } from '../../../typings/models'
8import { VideoShareModelOnly } from '../../../typings/models/video-share' 7import { MVideoShare } from '../../../typings/models/video'
9 8
10async function buildAnnounceWithVideoAudience ( 9async function buildAnnounceWithVideoAudience (
11 byActor: ActorModelOnly, 10 byActor: MActorLight,
12 videoShare: VideoShareModelOnly, 11 videoShare: MVideoShare,
13 video: VideoModel, 12 video: MVideo,
14 t: Transaction 13 t: Transaction
15) { 14) {
16 const announcedObject = video.url 15 const announcedObject = video.url
@@ -23,7 +22,7 @@ async function buildAnnounceWithVideoAudience (
23 return { activity, actorsInvolvedInVideo } 22 return { activity, actorsInvolvedInVideo }
24} 23}
25 24
26async function sendVideoAnnounce (byActor: ActorModelOnly, videoShare: VideoShareModelOnly, video: VideoModel, t: Transaction) { 25async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) {
27 const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t) 26 const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t)
28 27
29 logger.info('Creating job to send announce %s.', videoShare.url) 28 logger.info('Creating job to send announce %s.', videoShare.url)
@@ -32,7 +31,7 @@ async function sendVideoAnnounce (byActor: ActorModelOnly, videoShare: VideoShar
32 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException) 31 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException)
33} 32}
34 33
35function buildAnnounceActivity (url: string, byActor: ActorModelOnly, object: string, audience?: ActivityAudience): ActivityAnnounce { 34function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce {
36 if (!audience) audience = getAudience(byActor) 35 if (!audience) audience = getAudience(byActor)
37 36
38 return audiencify({ 37 return audiencify({
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index 9c21149f2..26ec3e948 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -1,19 +1,23 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
3import { VideoPrivacy } from '../../../../shared/models/videos' 3import { VideoPrivacy } from '../../../../shared/models/videos'
4import { ActorModel } from '../../../models/activitypub/actor'
5import { VideoModel } from '../../../models/video/video'
6import { VideoCommentModel } from '../../../models/video/video-comment' 4import { VideoCommentModel } from '../../../models/video/video-comment'
7import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' 5import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
8import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience' 6import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience'
9import { logger } from '../../../helpers/logger' 7import { logger } from '../../../helpers/logger'
10import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
11import { VideoPlaylistModel } from '../../../models/video/video-playlist'
12import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' 8import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
13import { getServerActor } from '../../../helpers/utils' 9import { getServerActor } from '../../../helpers/utils'
14import * as Bluebird from 'bluebird' 10import {
15 11 MActorLight,
16async function sendCreateVideo (video: VideoModel, t: Transaction) { 12 MCommentOwnerVideo,
13 MVideoAccountLight,
14 MVideoAP,
15 MVideoPlaylistFull,
16 MVideoRedundancyFileVideo,
17 MVideoRedundancyStreamingPlaylistVideo
18} from '../../../typings/models'
19
20async function sendCreateVideo (video: MVideoAP, t: Transaction) {
17 if (video.privacy === VideoPrivacy.PRIVATE) return undefined 21 if (video.privacy === VideoPrivacy.PRIVATE) return undefined
18 22
19 logger.info('Creating job to send video creation of %s.', video.url) 23 logger.info('Creating job to send video creation of %s.', video.url)
@@ -27,7 +31,11 @@ async function sendCreateVideo (video: VideoModel, t: Transaction) {
27 return broadcastToFollowers(createActivity, byActor, [ byActor ], t) 31 return broadcastToFollowers(createActivity, byActor, [ byActor ], t)
28} 32}
29 33
30async function sendCreateCacheFile (byActor: ActorModel, video: VideoModel, fileRedundancy: VideoRedundancyModel) { 34async function sendCreateCacheFile (
35 byActor: MActorLight,
36 video: MVideoAccountLight,
37 fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo
38) {
31 logger.info('Creating job to send file cache of %s.', fileRedundancy.url) 39 logger.info('Creating job to send file cache of %s.', fileRedundancy.url)
32 40
33 return sendVideoRelatedCreateActivity({ 41 return sendVideoRelatedCreateActivity({
@@ -38,7 +46,7 @@ async function sendCreateCacheFile (byActor: ActorModel, video: VideoModel, file
38 }) 46 })
39} 47}
40 48
41async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transaction) { 49async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transaction) {
42 if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined 50 if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
43 51
44 logger.info('Creating job to send create video playlist of %s.', playlist.url) 52 logger.info('Creating job to send create video playlist of %s.', playlist.url)
@@ -57,7 +65,7 @@ async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transac
57 return broadcastToFollowers(createActivity, byActor, toFollowersOf, t) 65 return broadcastToFollowers(createActivity, byActor, toFollowersOf, t)
58} 66}
59 67
60async function sendCreateVideoComment (comment: VideoCommentModel, t: Transaction) { 68async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transaction) {
61 logger.info('Creating job to send comment %s.', comment.url) 69 logger.info('Creating job to send comment %s.', comment.url)
62 70
63 const isOrigin = comment.Video.isOwned() 71 const isOrigin = comment.Video.isOwned()
@@ -95,7 +103,7 @@ async function sendCreateVideoComment (comment: VideoCommentModel, t: Transactio
95 t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) 103 t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl))
96} 104}
97 105
98function buildCreateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate { 106function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate {
99 if (!audience) audience = getAudience(byActor) 107 if (!audience) audience = getAudience(byActor)
100 108
101 return audiencify( 109 return audiencify(
@@ -122,8 +130,8 @@ export {
122// --------------------------------------------------------------------------- 130// ---------------------------------------------------------------------------
123 131
124async function sendVideoRelatedCreateActivity (options: { 132async function sendVideoRelatedCreateActivity (options: {
125 byActor: ActorModel, 133 byActor: MActorLight,
126 video: VideoModel, 134 video: MVideoAccountLight,
127 url: string, 135 url: string,
128 object: any, 136 object: any,
129 transaction?: Transaction 137 transaction?: Transaction
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts
index 6c7fb8449..4b1ff8dc5 100644
--- a/server/lib/activitypub/send/send-delete.ts
+++ b/server/lib/activitypub/send/send-delete.ts
@@ -1,17 +1,17 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub'
3import { ActorModel } from '../../../models/activitypub/actor' 3import { ActorModel } from '../../../models/activitypub/actor'
4import { VideoModel } from '../../../models/video/video'
5import { VideoCommentModel } from '../../../models/video/video-comment' 4import { VideoCommentModel } from '../../../models/video/video-comment'
6import { VideoShareModel } from '../../../models/video/video-share' 5import { VideoShareModel } from '../../../models/video/video-share'
7import { getDeleteActivityPubUrl } from '../url' 6import { getDeleteActivityPubUrl } from '../url'
8import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' 7import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
9import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience' 8import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience'
10import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
11import { VideoPlaylistModel } from '../../../models/video/video-playlist'
12import { getServerActor } from '../../../helpers/utils' 10import { getServerActor } from '../../../helpers/utils'
11import { MCommentOwnerVideoReply, MVideoAccountLight, MVideoPlaylistFullSummary } from '../../../typings/models/video'
12import { MActorUrl } from '../../../typings/models'
13 13
14async function sendDeleteVideo (video: VideoModel, transaction: Transaction) { 14async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transaction) {
15 logger.info('Creating job to broadcast delete of video %s.', video.url) 15 logger.info('Creating job to broadcast delete of video %s.', video.url)
16 16
17 const byActor = video.VideoChannel.Account.Actor 17 const byActor = video.VideoChannel.Account.Actor
@@ -42,7 +42,7 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
42 return broadcastToFollowers(activity, byActor, actorsInvolved, t) 42 return broadcastToFollowers(activity, byActor, actorsInvolved, t)
43} 43}
44 44
45async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) { 45async function sendDeleteVideoComment (videoComment: MCommentOwnerVideoReply, t: Transaction) {
46 logger.info('Creating job to send delete of comment %s.', videoComment.url) 46 logger.info('Creating job to send delete of comment %s.', videoComment.url)
47 47
48 const isVideoOrigin = videoComment.Video.isOwned() 48 const isVideoOrigin = videoComment.Video.isOwned()
@@ -74,7 +74,7 @@ async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Trans
74 t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) 74 t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl))
75} 75}
76 76
77async function sendDeleteVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) { 77async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) {
78 logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url) 78 logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url)
79 79
80 const byActor = videoPlaylist.OwnerAccount.Actor 80 const byActor = videoPlaylist.OwnerAccount.Actor
@@ -101,7 +101,7 @@ export {
101 101
102// --------------------------------------------------------------------------- 102// ---------------------------------------------------------------------------
103 103
104function buildDeleteActivity (url: string, object: string, byActor: ActorModel, audience?: ActivityAudience): ActivityDelete { 104function buildDeleteActivity (url: string, object: string, byActor: MActorUrl, audience?: ActivityAudience): ActivityDelete {
105 const activity = { 105 const activity = {
106 type: 'Delete' as 'Delete', 106 type: 'Delete' as 'Delete',
107 id: url, 107 id: url,
diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts
index a88436f2c..6e41f241f 100644
--- a/server/lib/activitypub/send/send-dislike.ts
+++ b/server/lib/activitypub/send/send-dislike.ts
@@ -1,13 +1,12 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActorModel } from '../../../models/activitypub/actor'
3import { VideoModel } from '../../../models/video/video'
4import { getVideoDislikeActivityPubUrl } from '../url' 2import { getVideoDislikeActivityPubUrl } from '../url'
5import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
6import { ActivityAudience, ActivityDislike } from '../../../../shared/models/activitypub' 4import { ActivityAudience, ActivityDislike } from '../../../../shared/models/activitypub'
7import { sendVideoRelatedActivity } from './utils' 5import { sendVideoRelatedActivity } from './utils'
8import { audiencify, getAudience } from '../audience' 6import { audiencify, getAudience } from '../audience'
7import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models'
9 8
10async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { 9async function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
11 logger.info('Creating job to dislike %s.', video.url) 10 logger.info('Creating job to dislike %s.', video.url)
12 11
13 const activityBuilder = (audience: ActivityAudience) => { 12 const activityBuilder = (audience: ActivityAudience) => {
@@ -19,7 +18,7 @@ async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transacti
19 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) 18 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
20} 19}
21 20
22function buildDislikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityDislike { 21function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike {
23 if (!audience) audience = getAudience(byActor) 22 if (!audience) audience = getAudience(byActor)
24 23
25 return audiencify( 24 return audiencify(
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts
index 61ee389a6..5ae1614ab 100644
--- a/server/lib/activitypub/send/send-flag.ts
+++ b/server/lib/activitypub/send/send-flag.ts
@@ -1,14 +1,13 @@
1import { ActorModel } from '../../../models/activitypub/actor'
2import { VideoModel } from '../../../models/video/video'
3import { VideoAbuseModel } from '../../../models/video/video-abuse'
4import { getVideoAbuseActivityPubUrl } from '../url' 1import { getVideoAbuseActivityPubUrl } from '../url'
5import { unicastTo } from './utils' 2import { unicastTo } from './utils'
6import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
7import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' 4import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub'
8import { audiencify, getAudience } from '../audience' 5import { audiencify, getAudience } from '../audience'
9import { Transaction } from 'sequelize' 6import { Transaction } from 'sequelize'
7import { MActor, MVideoFullLight } from '../../../typings/models'
8import { MVideoAbuseVideo } from '../../../typings/models/video'
10 9
11async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) { 10async function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) {
12 if (!video.VideoChannel.Account.Actor.serverId) return // Local user 11 if (!video.VideoChannel.Account.Actor.serverId) return // Local user
13 12
14 const url = getVideoAbuseActivityPubUrl(videoAbuse) 13 const url = getVideoAbuseActivityPubUrl(videoAbuse)
@@ -22,7 +21,7 @@ async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel,
22 t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)) 21 t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl))
23} 22}
24 23
25function buildFlagActivity (url: string, byActor: ActorModel, videoAbuse: VideoAbuseModel, audience: ActivityAudience): ActivityFlag { 24function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbuseVideo, audience: ActivityAudience): ActivityFlag {
26 if (!audience) audience = getAudience(byActor) 25 if (!audience) audience = getAudience(byActor)
27 26
28 const activity = Object.assign( 27 const activity = Object.assign(
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts
index a59ed50cf..6b17b25da 100644
--- a/server/lib/activitypub/send/send-follow.ts
+++ b/server/lib/activitypub/send/send-follow.ts
@@ -4,9 +4,9 @@ import { getActorFollowActivityPubUrl } from '../url'
4import { unicastTo } from './utils' 4import { unicastTo } from './utils'
5import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6import { Transaction } from 'sequelize' 6import { Transaction } from 'sequelize'
7import { ActorModelOnly } from '../../../typings/models' 7import { MActor, MActorFollowActors } from '../../../typings/models'
8 8
9function sendFollow (actorFollow: ActorFollowModel, t: Transaction) { 9function sendFollow (actorFollow: MActorFollowActors, t: Transaction) {
10 const me = actorFollow.ActorFollower 10 const me = actorFollow.ActorFollower
11 const following = actorFollow.ActorFollowing 11 const following = actorFollow.ActorFollowing
12 12
@@ -21,7 +21,7 @@ function sendFollow (actorFollow: ActorFollowModel, t: Transaction) {
21 t.afterCommit(() => unicastTo(data, me, following.inboxUrl)) 21 t.afterCommit(() => unicastTo(data, me, following.inboxUrl))
22} 22}
23 23
24function buildFollowActivity (url: string, byActor: ActorModelOnly, targetActor: ActorModelOnly): ActivityFollow { 24function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow {
25 return { 25 return {
26 type: 'Follow', 26 type: 'Follow',
27 id: url, 27 id: url,
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index 35227887a..e84a6f98b 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -1,13 +1,12 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub'
3import { ActorModel } from '../../../models/activitypub/actor'
4import { VideoModel } from '../../../models/video/video'
5import { getVideoLikeActivityPubUrl } from '../url' 3import { getVideoLikeActivityPubUrl } from '../url'
6import { sendVideoRelatedActivity } from './utils' 4import { sendVideoRelatedActivity } from './utils'
7import { audiencify, getAudience } from '../audience' 5import { audiencify, getAudience } from '../audience'
8import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
7import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models'
9 8
10async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) { 9async function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
11 logger.info('Creating job to like %s.', video.url) 10 logger.info('Creating job to like %s.', video.url)
12 11
13 const activityBuilder = (audience: ActivityAudience) => { 12 const activityBuilder = (audience: ActivityAudience) => {
@@ -19,7 +18,7 @@ async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction)
19 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) 18 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
20} 19}
21 20
22function buildLikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityLike { 21function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike {
23 if (!audience) audience = getAudience(byActor) 22 if (!audience) audience = getAudience(byActor)
24 23
25 return audiencify( 24 return audiencify(
diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts
index 63110b433..4258a3c36 100644
--- a/server/lib/activitypub/send/send-reject.ts
+++ b/server/lib/activitypub/send/send-reject.ts
@@ -1,12 +1,11 @@
1import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub' 1import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub'
2import { ActorModel } from '../../../models/activitypub/actor'
3import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url' 2import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url'
4import { unicastTo } from './utils' 3import { unicastTo } from './utils'
5import { buildFollowActivity } from './send-follow' 4import { buildFollowActivity } from './send-follow'
6import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
7import { SignatureActorModel } from '../../../typings/models' 6import { MActor } from '../../../typings/models'
8 7
9async function sendReject (follower: SignatureActorModel, following: ActorModel) { 8async function sendReject (follower: MActor, following: MActor) {
10 if (!follower.serverId) { // This should never happen 9 if (!follower.serverId) { // This should never happen
11 logger.warn('Do not sending reject to local follower.') 10 logger.warn('Do not sending reject to local follower.')
12 return 11 return
@@ -31,7 +30,7 @@ export {
31 30
32// --------------------------------------------------------------------------- 31// ---------------------------------------------------------------------------
33 32
34function buildRejectActivity (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityReject { 33function buildRejectActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityReject {
35 return { 34 return {
36 type: 'Reject', 35 type: 'Reject',
37 id: url, 36 id: url,
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index 8fcbbac5c..e9ab5b3c5 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -2,13 +2,12 @@ import { Transaction } from 'sequelize'
2import { 2import {
3 ActivityAnnounce, 3 ActivityAnnounce,
4 ActivityAudience, 4 ActivityAudience,
5 ActivityCreate, ActivityDislike, 5 ActivityCreate,
6 ActivityDislike,
6 ActivityFollow, 7 ActivityFollow,
7 ActivityLike, 8 ActivityLike,
8 ActivityUndo 9 ActivityUndo
9} from '../../../../shared/models/activitypub' 10} from '../../../../shared/models/activitypub'
10import { ActorModel } from '../../../models/activitypub/actor'
11import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
12import { VideoModel } from '../../../models/video/video' 11import { VideoModel } from '../../../models/video/video'
13import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' 12import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
14import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' 13import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
@@ -16,13 +15,20 @@ import { audiencify, getAudience } from '../audience'
16import { buildCreateActivity } from './send-create' 15import { buildCreateActivity } from './send-create'
17import { buildFollowActivity } from './send-follow' 16import { buildFollowActivity } from './send-follow'
18import { buildLikeActivity } from './send-like' 17import { buildLikeActivity } from './send-like'
19import { VideoShareModel } from '../../../models/video/video-share'
20import { buildAnnounceWithVideoAudience } from './send-announce' 18import { buildAnnounceWithVideoAudience } from './send-announce'
21import { logger } from '../../../helpers/logger' 19import { logger } from '../../../helpers/logger'
22import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
23import { buildDislikeActivity } from './send-dislike' 20import { buildDislikeActivity } from './send-dislike'
24 21import {
25async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { 22 MActor, MActorAudience,
23 MActorFollowActors,
24 MActorLight,
25 MVideo,
26 MVideoAccountLight,
27 MVideoRedundancyVideo,
28 MVideoShare
29} from '../../../typings/models'
30
31async function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) {
26 const me = actorFollow.ActorFollower 32 const me = actorFollow.ActorFollower
27 const following = actorFollow.ActorFollowing 33 const following = actorFollow.ActorFollowing
28 34
@@ -40,7 +46,7 @@ async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
40 t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl)) 46 t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl))
41} 47}
42 48
43async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { 49async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) {
44 logger.info('Creating job to undo announce %s.', videoShare.url) 50 logger.info('Creating job to undo announce %s.', videoShare.url)
45 51
46 const undoUrl = getUndoActivityPubUrl(videoShare.url) 52 const undoUrl = getUndoActivityPubUrl(videoShare.url)
@@ -52,7 +58,7 @@ async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareMode
52 return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException) 58 return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException)
53} 59}
54 60
55async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transaction) { 61async function sendUndoLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
56 logger.info('Creating job to undo a like of video %s.', video.url) 62 logger.info('Creating job to undo a like of video %s.', video.url)
57 63
58 const likeUrl = getVideoLikeActivityPubUrl(byActor, video) 64 const likeUrl = getVideoLikeActivityPubUrl(byActor, video)
@@ -61,7 +67,7 @@ async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transact
61 return sendUndoVideoRelatedActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t }) 67 return sendUndoVideoRelatedActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t })
62} 68}
63 69
64async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { 70async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
65 logger.info('Creating job to undo a dislike of video %s.', video.url) 71 logger.info('Creating job to undo a dislike of video %s.', video.url)
66 72
67 const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video) 73 const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video)
@@ -70,7 +76,7 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans
70 return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) 76 return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t })
71} 77}
72 78
73async function sendUndoCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel, t: Transaction) { 79async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, t: Transaction) {
74 logger.info('Creating job to undo cache file %s.', redundancyModel.url) 80 logger.info('Creating job to undo cache file %s.', redundancyModel.url)
75 81
76 const videoId = redundancyModel.getVideo().id 82 const videoId = redundancyModel.getVideo().id
@@ -94,7 +100,7 @@ export {
94 100
95function undoActivityData ( 101function undoActivityData (
96 url: string, 102 url: string,
97 byActor: ActorModel, 103 byActor: MActorAudience,
98 object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, 104 object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce,
99 audience?: ActivityAudience 105 audience?: ActivityAudience
100): ActivityUndo { 106): ActivityUndo {
@@ -112,8 +118,8 @@ function undoActivityData (
112} 118}
113 119
114async function sendUndoVideoRelatedActivity (options: { 120async function sendUndoVideoRelatedActivity (options: {
115 byActor: ActorModel, 121 byActor: MActor,
116 video: VideoModel, 122 video: MVideoAccountLight,
117 url: string, 123 url: string,
118 activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, 124 activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce,
119 transaction: Transaction 125 transaction: Transaction
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts
index 5bf092894..37517c2be 100644
--- a/server/lib/activitypub/send/send-update.ts
+++ b/server/lib/activitypub/send/send-update.ts
@@ -2,21 +2,29 @@ import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub'
3import { VideoPrivacy } from '../../../../shared/models/videos' 3import { VideoPrivacy } from '../../../../shared/models/videos'
4import { AccountModel } from '../../../models/account/account' 4import { AccountModel } from '../../../models/account/account'
5import { ActorModel } from '../../../models/activitypub/actor'
6import { VideoModel } from '../../../models/video/video' 5import { VideoModel } from '../../../models/video/video'
7import { VideoChannelModel } from '../../../models/video/video-channel'
8import { VideoShareModel } from '../../../models/video/video-share' 6import { VideoShareModel } from '../../../models/video/video-share'
9import { getUpdateActivityPubUrl } from '../url' 7import { getUpdateActivityPubUrl } from '../url'
10import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' 8import { broadcastToFollowers, sendVideoRelatedActivity } from './utils'
11import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' 9import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience'
12import { logger } from '../../../helpers/logger' 10import { logger } from '../../../helpers/logger'
13import { VideoCaptionModel } from '../../../models/video/video-caption' 11import { VideoCaptionModel } from '../../../models/video/video-caption'
14import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
15import { VideoPlaylistModel } from '../../../models/video/video-playlist'
16import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' 12import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
17import { getServerActor } from '../../../helpers/utils' 13import { getServerActor } from '../../../helpers/utils'
14import {
15 MAccountDefault,
16 MActor,
17 MActorLight,
18 MChannelDefault,
19 MVideoAP,
20 MVideoAPWithoutCaption,
21 MVideoPlaylistFull,
22 MVideoRedundancyVideo
23} from '../../../typings/models'
24
25async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) {
26 const video = videoArg as MVideoAP
18 27
19async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) {
20 if (video.privacy === VideoPrivacy.PRIVATE) return undefined 28 if (video.privacy === VideoPrivacy.PRIVATE) return undefined
21 29
22 logger.info('Creating job to update video %s.', video.url) 30 logger.info('Creating job to update video %s.', video.url)
@@ -41,7 +49,7 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByAct
41 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) 49 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t)
42} 50}
43 51
44async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelModel, t: Transaction) { 52async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, t: Transaction) {
45 const byActor = accountOrChannel.Actor 53 const byActor = accountOrChannel.Actor
46 54
47 logger.info('Creating job to update actor %s.', byActor.url) 55 logger.info('Creating job to update actor %s.', byActor.url)
@@ -51,7 +59,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod
51 const audience = getAudience(byActor) 59 const audience = getAudience(byActor)
52 const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience) 60 const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience)
53 61
54 let actorsInvolved: ActorModel[] 62 let actorsInvolved: MActor[]
55 if (accountOrChannel instanceof AccountModel) { 63 if (accountOrChannel instanceof AccountModel) {
56 // Actors that shared my videos are involved too 64 // Actors that shared my videos are involved too
57 actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) 65 actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t)
@@ -65,7 +73,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod
65 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) 73 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t)
66} 74}
67 75
68async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel) { 76async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
69 logger.info('Creating job to update cache file %s.', redundancyModel.url) 77 logger.info('Creating job to update cache file %s.', redundancyModel.url)
70 78
71 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.getVideo().id) 79 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.getVideo().id)
@@ -80,7 +88,7 @@ async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoR
80 return sendVideoRelatedActivity(activityBuilder, { byActor, video }) 88 return sendVideoRelatedActivity(activityBuilder, { byActor, video })
81} 89}
82 90
83async function sendUpdateVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) { 91async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) {
84 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined 92 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
85 93
86 const byActor = videoPlaylist.OwnerAccount.Actor 94 const byActor = videoPlaylist.OwnerAccount.Actor
@@ -113,7 +121,7 @@ export {
113 121
114// --------------------------------------------------------------------------- 122// ---------------------------------------------------------------------------
115 123
116function buildUpdateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityUpdate { 124function buildUpdateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityUpdate {
117 if (!audience) audience = getAudience(byActor) 125 if (!audience) audience = getAudience(byActor)
118 126
119 return audiencify( 127 return audiencify(
@@ -121,8 +129,7 @@ function buildUpdateActivity (url: string, byActor: ActorModel, object: any, aud
121 type: 'Update' as 'Update', 129 type: 'Update' as 'Update',
122 id: url, 130 id: url,
123 actor: byActor.url, 131 actor: byActor.url,
124 object: audiencify(object, audience 132 object: audiencify(object, audience)
125 )
126 }, 133 },
127 audience 134 audience
128 ) 135 )
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts
index 8ad126be0..8809417f9 100644
--- a/server/lib/activitypub/send/send-view.ts
+++ b/server/lib/activitypub/send/send-view.ts
@@ -1,13 +1,13 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub'
3import { ActorModel } from '../../../models/activitypub/actor' 3import { ActorModel } from '../../../models/activitypub/actor'
4import { VideoModel } from '../../../models/video/video'
5import { getVideoLikeActivityPubUrl } from '../url' 4import { getVideoLikeActivityPubUrl } from '../url'
6import { sendVideoRelatedActivity } from './utils' 5import { sendVideoRelatedActivity } from './utils'
7import { audiencify, getAudience } from '../audience' 6import { audiencify, getAudience } from '../audience'
8import { logger } from '../../../helpers/logger' 7import { logger } from '../../../helpers/logger'
8import { MActorAudience, MVideoAccountLight, MVideoUrl } from '@server/typings/models'
9 9
10async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction) { 10async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Transaction) {
11 logger.info('Creating job to send view of %s.', video.url) 11 logger.info('Creating job to send view of %s.', video.url)
12 12
13 const activityBuilder = (audience: ActivityAudience) => { 13 const activityBuilder = (audience: ActivityAudience) => {
@@ -19,7 +19,7 @@ async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction)
19 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) 19 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
20} 20}
21 21
22function buildViewActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityView { 22function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView {
23 if (!audience) audience = getAudience(byActor) 23 if (!audience) audience = getAudience(byActor)
24 24
25 return audiencify( 25 return audiencify(
diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts
index 4f69afb00..8129ab32a 100644
--- a/server/lib/activitypub/send/utils.ts
+++ b/server/lib/activitypub/send/utils.ts
@@ -4,15 +4,14 @@ import { logger } from '../../../helpers/logger'
4import { ActorModel } from '../../../models/activitypub/actor' 4import { ActorModel } from '../../../models/activitypub/actor'
5import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 5import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
6import { JobQueue } from '../../job-queue' 6import { JobQueue } from '../../job-queue'
7import { VideoModel } from '../../../models/video/video'
8import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' 7import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience'
9import { getServerActor } from '../../../helpers/utils' 8import { getServerActor } from '../../../helpers/utils'
10import { afterCommitIfTransaction } from '../../../helpers/database-utils' 9import { afterCommitIfTransaction } from '../../../helpers/database-utils'
11import { ActorFollowerException, ActorModelId, ActorModelOnly } from '../../../typings/models' 10import { MActorFollowerException, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models'
12 11
13async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { 12async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
14 byActor: ActorModelOnly, 13 byActor: MActorLight,
15 video: VideoModel, 14 video: MVideoAccountLight,
16 transaction?: Transaction 15 transaction?: Transaction
17}) { 16}) {
18 const { byActor, video, transaction } = options 17 const { byActor, video, transaction } = options
@@ -41,8 +40,8 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
41async function forwardVideoRelatedActivity ( 40async function forwardVideoRelatedActivity (
42 activity: Activity, 41 activity: Activity,
43 t: Transaction, 42 t: Transaction,
44 followersException: ActorFollowerException[] = [], 43 followersException: MActorFollowerException[] = [],
45 video: VideoModel 44 video: MVideo
46) { 45) {
47 // Mastodon does not add our announces in audience, so we forward to them manually 46 // Mastodon does not add our announces in audience, so we forward to them manually
48 const additionalActors = await getActorsInvolvedInVideo(video, t) 47 const additionalActors = await getActorsInvolvedInVideo(video, t)
@@ -54,7 +53,7 @@ async function forwardVideoRelatedActivity (
54async function forwardActivity ( 53async function forwardActivity (
55 activity: Activity, 54 activity: Activity,
56 t: Transaction, 55 t: Transaction,
57 followersException: ActorFollowerException[] = [], 56 followersException: MActorFollowerException[] = [],
58 additionalFollowerUrls: string[] = [] 57 additionalFollowerUrls: string[] = []
59) { 58) {
60 logger.info('Forwarding activity %s.', activity.id) 59 logger.info('Forwarding activity %s.', activity.id)
@@ -88,10 +87,10 @@ async function forwardActivity (
88 87
89async function broadcastToFollowers ( 88async function broadcastToFollowers (
90 data: any, 89 data: any,
91 byActor: ActorModelId, 90 byActor: MActorId,
92 toFollowersOf: ActorModelId[], 91 toFollowersOf: MActorId[],
93 t: Transaction, 92 t: Transaction,
94 actorsException: ActorFollowerException[] = [] 93 actorsException: MActorFollowerException[] = []
95) { 94) {
96 const uris = await computeFollowerUris(toFollowersOf, actorsException, t) 95 const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
97 96
@@ -100,16 +99,16 @@ async function broadcastToFollowers (
100 99
101async function broadcastToActors ( 100async function broadcastToActors (
102 data: any, 101 data: any,
103 byActor: ActorModelId, 102 byActor: MActorId,
104 toActors: ActorModelOnly[], 103 toActors: MActor[],
105 t?: Transaction, 104 t?: Transaction,
106 actorsException: ActorFollowerException[] = [] 105 actorsException: MActorFollowerException[] = []
107) { 106) {
108 const uris = await computeUris(toActors, actorsException) 107 const uris = await computeUris(toActors, actorsException)
109 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) 108 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor))
110} 109}
111 110
112function broadcastTo (uris: string[], data: any, byActor: ActorModelId) { 111function broadcastTo (uris: string[], data: any, byActor: MActorId) {
113 if (uris.length === 0) return undefined 112 if (uris.length === 0) return undefined
114 113
115 logger.debug('Creating broadcast job.', { uris }) 114 logger.debug('Creating broadcast job.', { uris })
@@ -123,7 +122,7 @@ function broadcastTo (uris: string[], data: any, byActor: ActorModelId) {
123 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) 122 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
124} 123}
125 124
126function unicastTo (data: any, byActor: ActorModelId, toActorUrl: string) { 125function unicastTo (data: any, byActor: MActorId, toActorUrl: string) {
127 logger.debug('Creating unicast job.', { uri: toActorUrl }) 126 logger.debug('Creating unicast job.', { uri: toActorUrl })
128 127
129 const payload = { 128 const payload = {
@@ -148,7 +147,7 @@ export {
148 147
149// --------------------------------------------------------------------------- 148// ---------------------------------------------------------------------------
150 149
151async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsException: ActorFollowerException[], t: Transaction) { 150async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorFollowerException[], t: Transaction) {
152 const toActorFollowerIds = toFollowersOf.map(a => a.id) 151 const toActorFollowerIds = toFollowersOf.map(a => a.id)
153 152
154 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) 153 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
@@ -157,7 +156,7 @@ async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsExcepti
157 return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) 156 return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
158} 157}
159 158
160async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFollowerException[] = []) { 159async function computeUris (toActors: MActor[], actorsException: MActorFollowerException[] = []) {
161 const serverActor = await getServerActor() 160 const serverActor = await getServerActor()
162 const targetUrls = toActors 161 const targetUrls = toActors
163 .filter(a => a.id !== serverActor.id) // Don't send to ourselves 162 .filter(a => a.id !== serverActor.id) // Don't send to ourselves
@@ -170,7 +169,7 @@ async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFo
170 .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) 169 .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
171} 170}
172 171
173async function buildSharedInboxesException (actorsException: ActorFollowerException[]) { 172async function buildSharedInboxesException (actorsException: MActorFollowerException[]) {
174 const serverActor = await getServerActor() 173 const serverActor = await getServerActor()
175 174
176 return actorsException 175 return actorsException
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts
index 7f38402b6..fdca9bed7 100644
--- a/server/lib/activitypub/share.ts
+++ b/server/lib/activitypub/share.ts
@@ -1,19 +1,18 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { VideoPrivacy } from '../../../shared/models/videos' 2import { VideoPrivacy } from '../../../shared/models/videos'
3import { getServerActor } from '../../helpers/utils' 3import { getServerActor } from '../../helpers/utils'
4import { VideoModel } from '../../models/video/video'
5import { VideoShareModel } from '../../models/video/video-share' 4import { VideoShareModel } from '../../models/video/video-share'
6import { sendUndoAnnounce, sendVideoAnnounce } from './send' 5import { sendUndoAnnounce, sendVideoAnnounce } from './send'
7import { getVideoAnnounceActivityPubUrl } from './url' 6import { getVideoAnnounceActivityPubUrl } from './url'
8import { VideoChannelModel } from '../../models/video/video-channel'
9import * as Bluebird from 'bluebird' 7import * as Bluebird from 'bluebird'
10import { doRequest } from '../../helpers/requests' 8import { doRequest } from '../../helpers/requests'
11import { getOrCreateActorAndServerAndModel } from './actor' 9import { getOrCreateActorAndServerAndModel } from './actor'
12import { logger } from '../../helpers/logger' 10import { logger } from '../../helpers/logger'
13import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' 11import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
14import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' 12import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
13import { MChannelActor, MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models/video'
15 14
16async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) { 15async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) {
17 if (video.privacy === VideoPrivacy.PRIVATE) return undefined 16 if (video.privacy === VideoPrivacy.PRIVATE) return undefined
18 17
19 return Promise.all([ 18 return Promise.all([
@@ -22,7 +21,11 @@ async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction)
22 ]) 21 ])
23} 22}
24 23
25async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) { 24async function changeVideoChannelShare (
25 video: MVideoAccountLight,
26 oldVideoChannel: MChannelActorLight,
27 t: Transaction
28) {
26 logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name) 29 logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name)
27 30
28 await undoShareByVideoChannel(video, oldVideoChannel, t) 31 await undoShareByVideoChannel(video, oldVideoChannel, t)
@@ -30,7 +33,7 @@ async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: Vide
30 await shareByVideoChannel(video, t) 33 await shareByVideoChannel(video, t)
31} 34}
32 35
33async function addVideoShares (shareUrls: string[], instance: VideoModel) { 36async function addVideoShares (shareUrls: string[], video: MVideoId) {
34 await Bluebird.map(shareUrls, async shareUrl => { 37 await Bluebird.map(shareUrls, async shareUrl => {
35 try { 38 try {
36 // Fetch url 39 // Fetch url
@@ -50,7 +53,7 @@ async function addVideoShares (shareUrls: string[], instance: VideoModel) {
50 53
51 const entry = { 54 const entry = {
52 actorId: actor.id, 55 actorId: actor.id,
53 videoId: instance.id, 56 videoId: video.id,
54 url: shareUrl 57 url: shareUrl
55 } 58 }
56 59
@@ -69,7 +72,7 @@ export {
69 72
70// --------------------------------------------------------------------------- 73// ---------------------------------------------------------------------------
71 74
72async function shareByServer (video: VideoModel, t: Transaction) { 75async function shareByServer (video: MVideo, t: Transaction) {
73 const serverActor = await getServerActor() 76 const serverActor = await getServerActor()
74 77
75 const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video) 78 const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video)
@@ -88,7 +91,7 @@ async function shareByServer (video: VideoModel, t: Transaction) {
88 return sendVideoAnnounce(serverActor, serverShare, video, t) 91 return sendVideoAnnounce(serverActor, serverShare, video, t)
89} 92}
90 93
91async function shareByVideoChannel (video: VideoModel, t: Transaction) { 94async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) {
92 const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video) 95 const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video)
93 const [ videoChannelShare ] = await VideoShareModel.findOrCreate({ 96 const [ videoChannelShare ] = await VideoShareModel.findOrCreate({
94 defaults: { 97 defaults: {
@@ -105,7 +108,7 @@ async function shareByVideoChannel (video: VideoModel, t: Transaction) {
105 return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t) 108 return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
106} 109}
107 110
108async function undoShareByVideoChannel (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) { 111async function undoShareByVideoChannel (video: MVideo, oldVideoChannel: MChannelActorLight, t: Transaction) {
109 // Load old share 112 // Load old share
110 const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t) 113 const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t)
111 if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id) 114 if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id)
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts
index dfcb3c668..6290af34b 100644
--- a/server/lib/activitypub/url.ts
+++ b/server/lib/activitypub/url.ts
@@ -1,36 +1,42 @@
1import { WEBSERVER } from '../../initializers/constants' 1import { WEBSERVER } from '../../initializers/constants'
2import { VideoModel } from '../../models/video/video' 2import {
3import { VideoAbuseModel } from '../../models/video/video-abuse' 3 MActor,
4import { VideoCommentModel } from '../../models/video/video-comment' 4 MActorFollowActors,
5import { VideoFileModel } from '../../models/video/video-file' 5 MActorId,
6import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' 6 MActorUrl,
7import { VideoPlaylistModel } from '../../models/video/video-playlist' 7 MCommentId,
8import { ActorModelOnly, ActorModelUrl } from '../../typings/models' 8 MVideoAbuseId,
9import { ActorFollowModelLight } from '../../typings/models/actor-follow' 9 MVideoId,
10 10 MVideoUrl,
11function getVideoActivityPubUrl (video: VideoModel) { 11 MVideoUUID
12} from '../../typings/models'
13import { MVideoPlaylist, MVideoPlaylistUUID } from '../../typings/models/video/video-playlist'
14import { MVideoFileVideoUUID } from '../../typings/models/video/video-file'
15import { MStreamingPlaylist } from '../../typings/models/video/video-streaming-playlist'
16
17function getVideoActivityPubUrl (video: MVideoUUID) {
12 return WEBSERVER.URL + '/videos/watch/' + video.uuid 18 return WEBSERVER.URL + '/videos/watch/' + video.uuid
13} 19}
14 20
15function getVideoPlaylistActivityPubUrl (videoPlaylist: VideoPlaylistModel) { 21function getVideoPlaylistActivityPubUrl (videoPlaylist: MVideoPlaylist) {
16 return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid 22 return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid
17} 23}
18 24
19function getVideoPlaylistElementActivityPubUrl (videoPlaylist: VideoPlaylistModel, video: VideoModel) { 25function getVideoPlaylistElementActivityPubUrl (videoPlaylist: MVideoPlaylistUUID, video: MVideoUUID) {
20 return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid 26 return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid
21} 27}
22 28
23function getVideoCacheFileActivityPubUrl (videoFile: VideoFileModel) { 29function getVideoCacheFileActivityPubUrl (videoFile: MVideoFileVideoUUID) {
24 const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : '' 30 const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : ''
25 31
26 return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}` 32 return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}`
27} 33}
28 34
29function getVideoCacheStreamingPlaylistActivityPubUrl (video: VideoModel, playlist: VideoStreamingPlaylistModel) { 35function getVideoCacheStreamingPlaylistActivityPubUrl (video: MVideoUUID, playlist: MStreamingPlaylist) {
30 return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}` 36 return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}`
31} 37}
32 38
33function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) { 39function getVideoCommentActivityPubUrl (video: MVideoUUID, videoComment: MCommentId) {
34 return WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id 40 return WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id
35} 41}
36 42
@@ -42,54 +48,54 @@ function getAccountActivityPubUrl (accountName: string) {
42 return WEBSERVER.URL + '/accounts/' + accountName 48 return WEBSERVER.URL + '/accounts/' + accountName
43} 49}
44 50
45function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) { 51function getVideoAbuseActivityPubUrl (videoAbuse: MVideoAbuseId) {
46 return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id 52 return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id
47} 53}
48 54
49function getVideoViewActivityPubUrl (byActor: ActorModelUrl, video: VideoModel) { 55function getVideoViewActivityPubUrl (byActor: MActorUrl, video: MVideoId) {
50 return byActor.url + '/views/videos/' + video.id + '/' + new Date().toISOString() 56 return byActor.url + '/views/videos/' + video.id + '/' + new Date().toISOString()
51} 57}
52 58
53function getVideoLikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) { 59function getVideoLikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) {
54 return byActor.url + '/likes/' + video.id 60 return byActor.url + '/likes/' + video.id
55} 61}
56 62
57function getVideoDislikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) { 63function getVideoDislikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) {
58 return byActor.url + '/dislikes/' + video.id 64 return byActor.url + '/dislikes/' + video.id
59} 65}
60 66
61function getVideoSharesActivityPubUrl (video: VideoModel) { 67function getVideoSharesActivityPubUrl (video: MVideoUrl) {
62 return video.url + '/announces' 68 return video.url + '/announces'
63} 69}
64 70
65function getVideoCommentsActivityPubUrl (video: VideoModel) { 71function getVideoCommentsActivityPubUrl (video: MVideoUrl) {
66 return video.url + '/comments' 72 return video.url + '/comments'
67} 73}
68 74
69function getVideoLikesActivityPubUrl (video: VideoModel) { 75function getVideoLikesActivityPubUrl (video: MVideoUrl) {
70 return video.url + '/likes' 76 return video.url + '/likes'
71} 77}
72 78
73function getVideoDislikesActivityPubUrl (video: VideoModel) { 79function getVideoDislikesActivityPubUrl (video: MVideoUrl) {
74 return video.url + '/dislikes' 80 return video.url + '/dislikes'
75} 81}
76 82
77function getActorFollowActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) { 83function getActorFollowActivityPubUrl (follower: MActor, following: MActorId) {
78 return follower.url + '/follows/' + following.id 84 return follower.url + '/follows/' + following.id
79} 85}
80 86
81function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModelLight) { 87function getActorFollowAcceptActivityPubUrl (actorFollow: MActorFollowActors) {
82 const follower = actorFollow.ActorFollower 88 const follower = actorFollow.ActorFollower
83 const me = actorFollow.ActorFollowing 89 const me = actorFollow.ActorFollowing
84 90
85 return follower.url + '/accepts/follows/' + me.id 91 return follower.url + '/accepts/follows/' + me.id
86} 92}
87 93
88function getActorFollowRejectActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) { 94function getActorFollowRejectActivityPubUrl (follower: MActorUrl, following: MActorId) {
89 return follower.url + '/rejects/follows/' + following.id 95 return follower.url + '/rejects/follows/' + following.id
90} 96}
91 97
92function getVideoAnnounceActivityPubUrl (byActor: ActorModelOnly, video: VideoModel) { 98function getVideoAnnounceActivityPubUrl (byActor: MActorId, video: MVideoUrl) {
93 return video.url + '/announces/' + byActor.id 99 return video.url + '/announces/' + byActor.id
94} 100}
95 101
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts
index 8d2c1ade3..3e8306fa4 100644
--- a/server/lib/activitypub/video-comments.ts
+++ b/server/lib/activitypub/video-comments.ts
@@ -2,20 +2,20 @@ import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validat
2import { logger } from '../../helpers/logger' 2import { logger } from '../../helpers/logger'
3import { doRequest } from '../../helpers/requests' 3import { doRequest } from '../../helpers/requests'
4import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' 4import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
5import { VideoModel } from '../../models/video/video'
6import { VideoCommentModel } from '../../models/video/video-comment' 5import { VideoCommentModel } from '../../models/video/video-comment'
7import { getOrCreateActorAndServerAndModel } from './actor' 6import { getOrCreateActorAndServerAndModel } from './actor'
8import { getOrCreateVideoAndAccountAndChannel } from './videos' 7import { getOrCreateVideoAndAccountAndChannel } from './videos'
9import * as Bluebird from 'bluebird' 8import * as Bluebird from 'bluebird'
10import { checkUrlsSameHost } from '../../helpers/activitypub' 9import { checkUrlsSameHost } from '../../helpers/activitypub'
10import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video'
11 11
12type ResolveThreadParams = { 12type ResolveThreadParams = {
13 url: string, 13 url: string,
14 comments?: VideoCommentModel[], 14 comments?: MCommentOwner[],
15 isVideo?: boolean, 15 isVideo?: boolean,
16 commentCreated?: boolean 16 commentCreated?: boolean
17} 17}
18type ResolveThreadResult = Promise<{ video: VideoModel, comment: VideoCommentModel, commentCreated: boolean }> 18type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }>
19 19
20async function addVideoComments (commentUrls: string[]) { 20async function addVideoComments (commentUrls: string[]) {
21 return Bluebird.map(commentUrls, commentUrl => { 21 return Bluebird.map(commentUrls, commentUrl => {
@@ -85,9 +85,9 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
85 const syncParam = { likes: true, dislikes: true, shares: true, comments: false, thumbnail: true, refreshVideo: false } 85 const syncParam = { likes: true, dislikes: true, shares: true, comments: false, thumbnail: true, refreshVideo: false }
86 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam }) 86 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam })
87 87
88 let resultComment: VideoCommentModel 88 let resultComment: MCommentOwnerVideo
89 if (comments.length !== 0) { 89 if (comments.length !== 0) {
90 const firstReply = comments[ comments.length - 1 ] 90 const firstReply = comments[ comments.length - 1 ] as MCommentOwnerVideo
91 firstReply.inReplyToCommentId = null 91 firstReply.inReplyToCommentId = null
92 firstReply.originCommentId = null 92 firstReply.originCommentId = null
93 firstReply.videoId = video.id 93 firstReply.videoId = video.id
@@ -97,7 +97,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
97 comments[comments.length - 1] = await firstReply.save() 97 comments[comments.length - 1] = await firstReply.save()
98 98
99 for (let i = comments.length - 2; i >= 0; i--) { 99 for (let i = comments.length - 2; i >= 0; i--) {
100 const comment = comments[ i ] 100 const comment = comments[ i ] as MCommentOwnerVideo
101 comment.originCommentId = firstReply.id 101 comment.originCommentId = firstReply.id
102 comment.inReplyToCommentId = comments[ i + 1 ].id 102 comment.inReplyToCommentId = comments[ i + 1 ].id
103 comment.videoId = video.id 103 comment.videoId = video.id
@@ -107,7 +107,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
107 comments[i] = await comment.save() 107 comments[i] = await comment.save()
108 } 108 }
109 109
110 resultComment = comments[0] 110 resultComment = comments[0] as MCommentOwnerVideo
111 } 111 }
112 112
113 return { video, comment: resultComment, commentCreated } 113 return { video, comment: resultComment, commentCreated }
@@ -151,7 +151,7 @@ async function resolveParentComment (params: ResolveThreadParams) {
151 originCommentId: null, 151 originCommentId: null,
152 createdAt: new Date(body.published), 152 createdAt: new Date(body.published),
153 updatedAt: new Date(body.updated) 153 updatedAt: new Date(body.updated)
154 }) 154 }) as MCommentOwner
155 comment.Account = actor.Account 155 comment.Account = actor.Account
156 156
157 return resolveThread({ 157 return resolveThread({
diff --git a/server/lib/activitypub/video-rates.ts b/server/lib/activitypub/video-rates.ts
index cda5b2981..6bd46bb58 100644
--- a/server/lib/activitypub/video-rates.ts
+++ b/server/lib/activitypub/video-rates.ts
@@ -1,6 +1,4 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { AccountModel } from '../../models/account/account'
3import { VideoModel } from '../../models/video/video'
4import { sendLike, sendUndoDislike, sendUndoLike } from './send' 2import { sendLike, sendUndoDislike, sendUndoLike } from './send'
5import { VideoRateType } from '../../../shared/models/videos' 3import { VideoRateType } from '../../../shared/models/videos'
6import * as Bluebird from 'bluebird' 4import * as Bluebird from 'bluebird'
@@ -10,11 +8,11 @@ import { logger } from '../../helpers/logger'
10import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' 8import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
11import { doRequest } from '../../helpers/requests' 9import { doRequest } from '../../helpers/requests'
12import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' 10import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
13import { ActorModel } from '../../models/activitypub/actor'
14import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url' 11import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url'
15import { sendDislike } from './send/send-dislike' 12import { sendDislike } from './send/send-dislike'
13import { MAccountActor, MActorUrl, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models'
16 14
17async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRateType) { 15async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateType) {
18 let rateCounts = 0 16 let rateCounts = 0
19 17
20 await Bluebird.map(ratesUrl, async rateUrl => { 18 await Bluebird.map(ratesUrl, async rateUrl => {
@@ -64,11 +62,13 @@ async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRa
64 return 62 return
65} 63}
66 64
67async function sendVideoRateChange (account: AccountModel, 65async function sendVideoRateChange (
68 video: VideoModel, 66 account: MAccountActor,
69 likes: number, 67 video: MVideoAccountLight,
70 dislikes: number, 68 likes: number,
71 t: Transaction) { 69 dislikes: number,
70 t: Transaction
71) {
72 const actor = account.Actor 72 const actor = account.Actor
73 73
74 // Keep the order: first we undo and then we create 74 // Keep the order: first we undo and then we create
@@ -84,8 +84,10 @@ async function sendVideoRateChange (account: AccountModel,
84 if (dislikes > 0) await sendDislike(actor, video, t) 84 if (dislikes > 0) await sendDislike(actor, video, t)
85} 85}
86 86
87function getRateUrl (rateType: VideoRateType, actor: ActorModel, video: VideoModel) { 87function getRateUrl (rateType: VideoRateType, actor: MActorUrl, video: MVideoId) {
88 return rateType === 'like' ? getVideoLikeActivityPubUrl(actor, video) : getVideoDislikeActivityPubUrl(actor, video) 88 return rateType === 'like'
89 ? getVideoLikeActivityPubUrl(actor, video)
90 : getVideoDislikeActivityPubUrl(actor, video)
89} 91}
90 92
91export { 93export {
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index 3a8451a32..c318978fd 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -24,7 +24,6 @@ import {
24 REMOTE_SCHEME, 24 REMOTE_SCHEME,
25 STATIC_PATHS 25 STATIC_PATHS
26} from '../../initializers/constants' 26} from '../../initializers/constants'
27import { ActorModel } from '../../models/activitypub/actor'
28import { TagModel } from '../../models/video/tag' 27import { TagModel } from '../../models/video/tag'
29import { VideoModel } from '../../models/video/video' 28import { VideoModel } from '../../models/video/video'
30import { VideoFileModel } from '../../models/video/video-file' 29import { VideoFileModel } from '../../models/video/video-file'
@@ -38,7 +37,6 @@ import { JobQueue } from '../job-queue'
38import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher' 37import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher'
39import { createRates } from './video-rates' 38import { createRates } from './video-rates'
40import { addVideoShares, shareVideoByServerAndChannel } from './share' 39import { addVideoShares, shareVideoByServerAndChannel } from './share'
41import { AccountModel } from '../../models/account/account'
42import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' 40import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video'
43import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' 41import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
44import { Notifier } from '../notifier' 42import { Notifier } from '../notifier'
@@ -49,15 +47,31 @@ import { VideoShareModel } from '../../models/video/video-share'
49import { VideoCommentModel } from '../../models/video/video-comment' 47import { VideoCommentModel } from '../../models/video/video-comment'
50import { sequelizeTypescript } from '../../initializers/database' 48import { sequelizeTypescript } from '../../initializers/database'
51import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' 49import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail'
52import { ThumbnailModel } from '../../models/video/thumbnail'
53import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' 50import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
54import { join } from 'path' 51import { join } from 'path'
55import { FilteredModelAttributes } from '../../typings/sequelize' 52import { FilteredModelAttributes } from '../../typings/sequelize'
56import { autoBlacklistVideoIfNeeded } from '../video-blacklist' 53import { autoBlacklistVideoIfNeeded } from '../video-blacklist'
57import { ActorFollowScoreCache } from '../files-cache' 54import { ActorFollowScoreCache } from '../files-cache'
58import { AccountModelIdActor, VideoChannelModelId, VideoChannelModelIdActor } from '../../typings/models' 55import {
56 MAccountIdActor,
57 MChannelAccountLight,
58 MChannelDefault,
59 MChannelId,
60 MVideo,
61 MVideoAccountLight,
62 MVideoAccountLightBlacklistAllFiles,
63 MVideoAP,
64 MVideoAPWithoutCaption,
65 MVideoFile,
66 MVideoFullLight,
67 MVideoId,
68 MVideoThumbnail
69} from '../../typings/models'
70import { MThumbnail } from '../../typings/models/video/thumbnail'
71
72async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) {
73 const video = videoArg as MVideoAP
59 74
60async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
61 if ( 75 if (
62 // Check this is not a blacklisted video, or unfederated blacklisted video 76 // Check this is not a blacklisted video, or unfederated blacklisted video
63 (video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) && 77 (video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) &&
@@ -102,7 +116,7 @@ async function fetchRemoteVideo (videoUrl: string): Promise<{ response: request.
102 return { response, videoObject: body } 116 return { response, videoObject: body }
103} 117}
104 118
105async function fetchRemoteVideoDescription (video: VideoModel) { 119async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
106 const host = video.VideoChannel.Account.Actor.Server.host 120 const host = video.VideoChannel.Account.Actor.Server.host
107 const path = video.getDescriptionAPIPath() 121 const path = video.getDescriptionAPIPath()
108 const options = { 122 const options = {
@@ -114,14 +128,14 @@ async function fetchRemoteVideoDescription (video: VideoModel) {
114 return body.description ? body.description : '' 128 return body.description ? body.description : ''
115} 129}
116 130
117function fetchRemoteVideoStaticFile (video: VideoModel, path: string, destPath: string) { 131function fetchRemoteVideoStaticFile (video: MVideoAccountLight, path: string, destPath: string) {
118 const url = buildRemoteBaseUrl(video, path) 132 const url = buildRemoteBaseUrl(video, path)
119 133
120 // We need to provide a callback, if no we could have an uncaught exception 134 // We need to provide a callback, if no we could have an uncaught exception
121 return doRequestAndSaveToFile({ uri: url }, destPath) 135 return doRequestAndSaveToFile({ uri: url }, destPath)
122} 136}
123 137
124function buildRemoteBaseUrl (video: VideoModel, path: string) { 138function buildRemoteBaseUrl (video: MVideoAccountLight, path: string) {
125 const host = video.VideoChannel.Account.Actor.Server.host 139 const host = video.VideoChannel.Account.Actor.Server.host
126 140
127 return REMOTE_SCHEME.HTTP + '://' + host + path 141 return REMOTE_SCHEME.HTTP + '://' + host + path
@@ -146,7 +160,7 @@ type SyncParam = {
146 thumbnail: boolean 160 thumbnail: boolean
147 refreshVideo?: boolean 161 refreshVideo?: boolean
148} 162}
149async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) { 163async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) {
150 logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid) 164 logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid)
151 165
152 const jobPayloads: ActivitypubHttpFetcherPayload[] = [] 166 const jobPayloads: ActivitypubHttpFetcherPayload[] = []
@@ -194,12 +208,24 @@ async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: Vid
194 await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) 208 await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }))
195} 209}
196 210
211function getOrCreateVideoAndAccountAndChannel (options: {
212 videoObject: { id: string } | string,
213 syncParam?: SyncParam,
214 fetchType?: 'all',
215 allowRefresh?: boolean
216}): Promise<{ video: MVideoAccountLightBlacklistAllFiles, created: boolean, autoBlacklisted?: boolean }>
217function getOrCreateVideoAndAccountAndChannel (options: {
218 videoObject: { id: string } | string,
219 syncParam?: SyncParam,
220 fetchType?: VideoFetchByUrlType,
221 allowRefresh?: boolean
222}): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }>
197async function getOrCreateVideoAndAccountAndChannel (options: { 223async function getOrCreateVideoAndAccountAndChannel (options: {
198 videoObject: { id: string } | string, 224 videoObject: { id: string } | string,
199 syncParam?: SyncParam, 225 syncParam?: SyncParam,
200 fetchType?: VideoFetchByUrlType, 226 fetchType?: VideoFetchByUrlType,
201 allowRefresh?: boolean // true by default 227 allowRefresh?: boolean // true by default
202}) { 228}): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> {
203 // Default params 229 // Default params
204 const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } 230 const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false }
205 const fetchType = options.fetchType || 'all' 231 const fetchType = options.fetchType || 'all'
@@ -227,8 +253,9 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
227 const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl) 253 const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl)
228 if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl) 254 if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
229 255
230 const channelActor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo) 256 const actor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo)
231 const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, channelActor, syncParam.thumbnail) 257 const videoChannel = actor.VideoChannel
258 const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, videoChannel, syncParam.thumbnail)
232 259
233 await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam) 260 await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam)
234 261
@@ -236,22 +263,22 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
236} 263}
237 264
238async function updateVideoFromAP (options: { 265async function updateVideoFromAP (options: {
239 video: VideoModel, 266 video: MVideoAccountLightBlacklistAllFiles,
240 videoObject: VideoTorrentObject, 267 videoObject: VideoTorrentObject,
241 account: AccountModelIdActor, 268 account: MAccountIdActor,
242 channel: VideoChannelModelIdActor, 269 channel: MChannelDefault,
243 overrideTo?: string[] 270 overrideTo?: string[]
244}) { 271}) {
245 const { video, videoObject, account, channel, overrideTo } = options 272 const { video, videoObject, account, channel, overrideTo } = options
246 273
247 logger.debug('Updating remote video "%s".', options.videoObject.uuid) 274 logger.debug('Updating remote video "%s".', options.videoObject.uuid, { account, channel })
248 275
249 let videoFieldsSave: any 276 let videoFieldsSave: any
250 const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE 277 const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE
251 const wasUnlistedVideo = video.privacy === VideoPrivacy.UNLISTED 278 const wasUnlistedVideo = video.privacy === VideoPrivacy.UNLISTED
252 279
253 try { 280 try {
254 let thumbnailModel: ThumbnailModel 281 let thumbnailModel: MThumbnail
255 282
256 try { 283 try {
257 thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) 284 thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE)
@@ -259,7 +286,7 @@ async function updateVideoFromAP (options: {
259 logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) 286 logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })
260 } 287 }
261 288
262 await sequelizeTypescript.transaction(async t => { 289 const videoUpdated = await sequelizeTypescript.transaction(async t => {
263 const sequelizeOptions = { transaction: t } 290 const sequelizeOptions = { transaction: t }
264 291
265 videoFieldsSave = video.toJSON() 292 videoFieldsSave = video.toJSON()
@@ -293,21 +320,21 @@ async function updateVideoFromAP (options: {
293 video.channelId = videoData.channelId 320 video.channelId = videoData.channelId
294 video.views = videoData.views 321 video.views = videoData.views
295 322
296 await video.save(sequelizeOptions) 323 const videoUpdated = await video.save(sequelizeOptions) as MVideoFullLight
297 324
298 if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t) 325 if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t)
299 326
300 // FIXME: use icon URL instead 327 // FIXME: use icon URL instead
301 const previewUrl = buildRemoteBaseUrl(video, join(STATIC_PATHS.PREVIEWS, video.getPreview().filename)) 328 const previewUrl = buildRemoteBaseUrl(videoUpdated, join(STATIC_PATHS.PREVIEWS, videoUpdated.getPreview().filename))
302 const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) 329 const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
303 await video.addAndSaveThumbnail(previewModel, t) 330 await videoUpdated.addAndSaveThumbnail(previewModel, t)
304 331
305 { 332 {
306 const videoFileAttributes = videoFileActivityUrlToDBAttributes(video, videoObject) 333 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoUpdated, videoObject)
307 const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a)) 334 const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a))
308 335
309 // Remove video files that do not exist anymore 336 // Remove video files that do not exist anymore
310 const destroyTasks = video.VideoFiles 337 const destroyTasks = videoUpdated.VideoFiles
311 .filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f))) 338 .filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f)))
312 .map(f => f.destroy(sequelizeOptions)) 339 .map(f => f.destroy(sequelizeOptions))
313 await Promise.all(destroyTasks) 340 await Promise.all(destroyTasks)
@@ -318,15 +345,15 @@ async function updateVideoFromAP (options: {
318 .then(([ file ]) => file) 345 .then(([ file ]) => file)
319 }) 346 })
320 347
321 video.VideoFiles = await Promise.all(upsertTasks) 348 videoUpdated.VideoFiles = await Promise.all(upsertTasks)
322 } 349 }
323 350
324 { 351 {
325 const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(video, videoObject, video.VideoFiles) 352 const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(videoUpdated, videoObject, videoUpdated.VideoFiles)
326 const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a)) 353 const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a))
327 354
328 // Remove video files that do not exist anymore 355 // Remove video files that do not exist anymore
329 const destroyTasks = video.VideoStreamingPlaylists 356 const destroyTasks = videoUpdated.VideoStreamingPlaylists
330 .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f))) 357 .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f)))
331 .map(f => f.destroy(sequelizeOptions)) 358 .map(f => f.destroy(sequelizeOptions))
332 await Promise.all(destroyTasks) 359 await Promise.all(destroyTasks)
@@ -337,38 +364,42 @@ async function updateVideoFromAP (options: {
337 .then(([ streamingPlaylist ]) => streamingPlaylist) 364 .then(([ streamingPlaylist ]) => streamingPlaylist)
338 }) 365 })
339 366
340 video.VideoStreamingPlaylists = await Promise.all(upsertTasks) 367 videoUpdated.VideoStreamingPlaylists = await Promise.all(upsertTasks)
341 } 368 }
342 369
343 { 370 {
344 // Update Tags 371 // Update Tags
345 const tags = videoObject.tag.map(tag => tag.name) 372 const tags = videoObject.tag.map(tag => tag.name)
346 const tagInstances = await TagModel.findOrCreateTags(tags, t) 373 const tagInstances = await TagModel.findOrCreateTags(tags, t)
347 await video.$set('Tags', tagInstances, sequelizeOptions) 374 await videoUpdated.$set('Tags', tagInstances, sequelizeOptions)
348 } 375 }
349 376
350 { 377 {
351 // Update captions 378 // Update captions
352 await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(video.id, t) 379 await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t)
353 380
354 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { 381 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
355 return VideoCaptionModel.insertOrReplaceLanguage(video.id, c.identifier, t) 382 return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, t)
356 }) 383 })
357 video.VideoCaptions = await Promise.all(videoCaptionsPromises) 384 await Promise.all(videoCaptionsPromises)
358 } 385 }
386
387 return videoUpdated
359 }) 388 })
360 389
361 await autoBlacklistVideoIfNeeded({ 390 await autoBlacklistVideoIfNeeded({
362 video, 391 video: videoUpdated,
363 user: undefined, 392 user: undefined,
364 isRemote: true, 393 isRemote: true,
365 isNew: false, 394 isNew: false,
366 transaction: undefined 395 transaction: undefined
367 }) 396 })
368 397
369 if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(video) // Notify our users? 398 if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated) // Notify our users?
370 399
371 logger.info('Remote video with uuid %s updated', videoObject.uuid) 400 logger.info('Remote video with uuid %s updated', videoObject.uuid)
401
402 return videoUpdated
372 } catch (err) { 403 } catch (err) {
373 if (video !== undefined && videoFieldsSave !== undefined) { 404 if (video !== undefined && videoFieldsSave !== undefined) {
374 resetSequelizeInstance(video, videoFieldsSave) 405 resetSequelizeInstance(video, videoFieldsSave)
@@ -381,15 +412,15 @@ async function updateVideoFromAP (options: {
381} 412}
382 413
383async function refreshVideoIfNeeded (options: { 414async function refreshVideoIfNeeded (options: {
384 video: VideoModel, 415 video: MVideoThumbnail,
385 fetchedType: VideoFetchByUrlType, 416 fetchedType: VideoFetchByUrlType,
386 syncParam: SyncParam 417 syncParam: SyncParam
387}): Promise<VideoModel> { 418}): Promise<MVideoThumbnail> {
388 if (!options.video.isOutdated()) return options.video 419 if (!options.video.isOutdated()) return options.video
389 420
390 // We need more attributes if the argument video was fetched with not enough joints 421 // We need more attributes if the argument video was fetched with not enough joints
391 const video = options.fetchedType === 'all' 422 const video = options.fetchedType === 'all'
392 ? options.video 423 ? options.video as MVideoAccountLightBlacklistAllFiles
393 : await VideoModel.loadByUrlAndPopulateAccount(options.video.url) 424 : await VideoModel.loadByUrlAndPopulateAccount(options.video.url)
394 425
395 try { 426 try {
@@ -410,12 +441,11 @@ async function refreshVideoIfNeeded (options: {
410 } 441 }
411 442
412 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) 443 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
413 const account = await AccountModel.load(channelActor.VideoChannel.accountId)
414 444
415 const updateOptions = { 445 const updateOptions = {
416 video, 446 video,
417 videoObject, 447 videoObject,
418 account, 448 account: channelActor.VideoChannel.Account,
419 channel: channelActor.VideoChannel 449 channel: channelActor.VideoChannel
420 } 450 }
421 await retryTransactionWrapper(updateVideoFromAP, updateOptions) 451 await retryTransactionWrapper(updateVideoFromAP, updateOptions)
@@ -467,15 +497,15 @@ function isAPPlaylistSegmentHashesUrlObject (tag: any): tag is ActivityPlaylistS
467 return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json' 497 return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json'
468} 498}
469 499
470async function createVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) { 500async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAccountLight, waitThumbnail = false) {
471 logger.debug('Adding remote video %s.', videoObject.id) 501 logger.debug('Adding remote video %s.', videoObject.id)
472 502
473 const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to) 503 const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to)
474 const video = VideoModel.build(videoData) 504 const video = VideoModel.build(videoData) as MVideoThumbnail
475 505
476 const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) 506 const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE)
477 507
478 let thumbnailModel: ThumbnailModel 508 let thumbnailModel: MThumbnail
479 if (waitThumbnail === true) { 509 if (waitThumbnail === true) {
480 thumbnailModel = await promiseThumbnail 510 thumbnailModel = await promiseThumbnail
481 } 511 }
@@ -483,8 +513,8 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
483 const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => { 513 const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => {
484 const sequelizeOptions = { transaction: t } 514 const sequelizeOptions = { transaction: t }
485 515
486 const videoCreated = await video.save(sequelizeOptions) 516 const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
487 videoCreated.VideoChannel = channelActor.VideoChannel 517 videoCreated.VideoChannel = channel
488 518
489 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) 519 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
490 520
@@ -517,15 +547,14 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
517 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { 547 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
518 return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t) 548 return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t)
519 }) 549 })
520 const captions = await Promise.all(videoCaptionsPromises) 550 await Promise.all(videoCaptionsPromises)
521 551
522 video.VideoFiles = videoFiles 552 videoCreated.VideoFiles = videoFiles
523 video.VideoStreamingPlaylists = streamingPlaylists 553 videoCreated.VideoStreamingPlaylists = streamingPlaylists
524 video.Tags = tagInstances 554 videoCreated.Tags = tagInstances
525 video.VideoCaptions = captions
526 555
527 const autoBlacklisted = await autoBlacklistVideoIfNeeded({ 556 const autoBlacklisted = await autoBlacklistVideoIfNeeded({
528 video, 557 video: videoCreated,
529 user: undefined, 558 user: undefined,
530 isRemote: true, 559 isRemote: true,
531 isNew: true, 560 isNew: true,
@@ -548,11 +577,7 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
548 return { autoBlacklisted, videoCreated } 577 return { autoBlacklisted, videoCreated }
549} 578}
550 579
551async function videoActivityObjectToDBAttributes ( 580async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) {
552 videoChannel: VideoChannelModelId,
553 videoObject: VideoTorrentObject,
554 to: string[] = []
555) {
556 const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED 581 const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED
557 const duration = videoObject.duration.replace(/[^\d]+/, '') 582 const duration = videoObject.duration.replace(/[^\d]+/, '')
558 583
@@ -603,7 +628,7 @@ async function videoActivityObjectToDBAttributes (
603 } 628 }
604} 629}
605 630
606function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) { 631function videoFileActivityUrlToDBAttributes (video: MVideo, videoObject: VideoTorrentObject) {
607 const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[] 632 const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[]
608 633
609 if (fileUrls.length === 0) { 634 if (fileUrls.length === 0) {
@@ -641,7 +666,7 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid
641 return attributes 666 return attributes
642} 667}
643 668
644function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject, videoFiles: VideoFileModel[]) { 669function streamingPlaylistActivityUrlToDBAttributes (video: MVideoId, videoObject: VideoTorrentObject, videoFiles: MVideoFile[]) {
645 const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[] 670 const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[]
646 if (playlistUrls.length === 0) return [] 671 if (playlistUrls.length === 0) return []
647 672
diff --git a/server/lib/avatar.ts b/server/lib/avatar.ts
index 1b38e6cb5..ad4cdd3ab 100644
--- a/server/lib/avatar.ts
+++ b/server/lib/avatar.ts
@@ -3,8 +3,6 @@ import { sendUpdateActor } from './activitypub/send'
3import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' 3import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants'
4import { updateActorAvatarInstance } from './activitypub' 4import { updateActorAvatarInstance } from './activitypub'
5import { processImage } from '../helpers/image-utils' 5import { processImage } from '../helpers/image-utils'
6import { AccountModel } from '../models/account/account'
7import { VideoChannelModel } from '../models/video/video-channel'
8import { extname, join } from 'path' 6import { extname, join } from 'path'
9import { retryTransactionWrapper } from '../helpers/database-utils' 7import { retryTransactionWrapper } from '../helpers/database-utils'
10import * as uuidv4 from 'uuid/v4' 8import * as uuidv4 from 'uuid/v4'
@@ -13,8 +11,12 @@ import { sequelizeTypescript } from '../initializers/database'
13import * as LRUCache from 'lru-cache' 11import * as LRUCache from 'lru-cache'
14import { queue } from 'async' 12import { queue } from 'async'
15import { downloadImage } from '../helpers/requests' 13import { downloadImage } from '../helpers/requests'
14import { MAccountDefault, MChannelDefault } from '../typings/models'
16 15
17async function updateActorAvatarFile (avatarPhysicalFile: Express.Multer.File, accountOrChannel: AccountModel | VideoChannelModel) { 16async function updateActorAvatarFile (
17 avatarPhysicalFile: Express.Multer.File,
18 accountOrChannel: MAccountDefault | MChannelDefault
19) {
18 const extension = extname(avatarPhysicalFile.filename) 20 const extension = extname(avatarPhysicalFile.filename)
19 const avatarName = uuidv4() + extension 21 const avatarName = uuidv4() + extension
20 const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) 22 const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
diff --git a/server/lib/blocklist.ts b/server/lib/blocklist.ts
index 1633e500c..28c69b46e 100644
--- a/server/lib/blocklist.ts
+++ b/server/lib/blocklist.ts
@@ -1,6 +1,7 @@
1import { sequelizeTypescript } from '../initializers' 1import { sequelizeTypescript } from '../initializers'
2import { AccountBlocklistModel } from '../models/account/account-blocklist' 2import { AccountBlocklistModel } from '../models/account/account-blocklist'
3import { ServerBlocklistModel } from '../models/server/server-blocklist' 3import { ServerBlocklistModel } from '../models/server/server-blocklist'
4import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models'
4 5
5function addAccountInBlocklist (byAccountId: number, targetAccountId: number) { 6function addAccountInBlocklist (byAccountId: number, targetAccountId: number) {
6 return sequelizeTypescript.transaction(async t => { 7 return sequelizeTypescript.transaction(async t => {
@@ -20,13 +21,13 @@ function addServerInBlocklist (byAccountId: number, targetServerId: number) {
20 }) 21 })
21} 22}
22 23
23function removeAccountFromBlocklist (accountBlock: AccountBlocklistModel) { 24function removeAccountFromBlocklist (accountBlock: MAccountBlocklist) {
24 return sequelizeTypescript.transaction(async t => { 25 return sequelizeTypescript.transaction(async t => {
25 return accountBlock.destroy({ transaction: t }) 26 return accountBlock.destroy({ transaction: t })
26 }) 27 })
27} 28}
28 29
29function removeServerFromBlocklist (serverBlock: ServerBlocklistModel) { 30function removeServerFromBlocklist (serverBlock: MServerBlocklist) {
30 return sequelizeTypescript.transaction(async t => { 31 return sequelizeTypescript.transaction(async t => {
31 return serverBlock.destroy({ transaction: t }) 32 return serverBlock.destroy({ transaction: t })
32 }) 33 })
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index 8841dd2ac..a1f4ae858 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -13,6 +13,7 @@ import { VideoChannelModel } from '../models/video/video-channel'
13import * as Bluebird from 'bluebird' 13import * as Bluebird from 'bluebird'
14import { CONFIG } from '../initializers/config' 14import { CONFIG } from '../initializers/config'
15import { logger } from '../helpers/logger' 15import { logger } from '../helpers/logger'
16import { MAccountActor, MChannelActor, MVideo } from '../typings/models'
16 17
17export class ClientHtml { 18export class ClientHtml {
18 19
@@ -41,11 +42,11 @@ export class ClientHtml {
41 42
42 const [ html, video ] = await Promise.all([ 43 const [ html, video ] = await Promise.all([
43 ClientHtml.getIndexHTML(req, res), 44 ClientHtml.getIndexHTML(req, res),
44 VideoModel.load(videoId) 45 VideoModel.loadWithBlacklist(videoId)
45 ]) 46 ])
46 47
47 // Let Angular application handle errors 48 // Let Angular application handle errors
48 if (!video || video.privacy === VideoPrivacy.PRIVATE) { 49 if (!video || video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) {
49 return ClientHtml.getIndexHTML(req, res) 50 return ClientHtml.getIndexHTML(req, res)
50 } 51 }
51 52
@@ -65,7 +66,7 @@ export class ClientHtml {
65 } 66 }
66 67
67 private static async getAccountOrChannelHTMLPage ( 68 private static async getAccountOrChannelHTMLPage (
68 loader: () => Bluebird<AccountModel | VideoChannelModel>, 69 loader: () => Bluebird<MAccountActor | MChannelActor>,
69 req: express.Request, 70 req: express.Request,
70 res: express.Response 71 res: express.Response
71 ) { 72 ) {
@@ -157,7 +158,7 @@ export class ClientHtml {
157 return htmlStringPage.replace('</head>', linkTag + '</head>') 158 return htmlStringPage.replace('</head>', linkTag + '</head>')
158 } 159 }
159 160
160 private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) { 161 private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: MVideo) {
161 const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath() 162 const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath()
162 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 163 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
163 164
@@ -236,7 +237,7 @@ export class ClientHtml {
236 return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString) 237 return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString)
237 } 238 }
238 239
239 private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: AccountModel | VideoChannelModel) { 240 private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: MAccountActor | MChannelActor) {
240 // SEO, use origin account or channel URL 241 // SEO, use origin account or channel URL
241 const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />` 242 const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />`
242 243
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index 10e7d0479..76349ef8f 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -2,17 +2,13 @@ import { createTransport, Transporter } from 'nodemailer'
2import { isTestInstance } from '../helpers/core-utils' 2import { isTestInstance } from '../helpers/core-utils'
3import { bunyanLogger, logger } from '../helpers/logger' 3import { bunyanLogger, logger } from '../helpers/logger'
4import { CONFIG } from '../initializers/config' 4import { CONFIG } from '../initializers/config'
5import { UserModel } from '../models/account/user'
6import { VideoModel } from '../models/video/video'
7import { JobQueue } from './job-queue' 5import { JobQueue } from './job-queue'
8import { EmailPayload } from './job-queue/handlers/email' 6import { EmailPayload } from './job-queue/handlers/email'
9import { readFileSync } from 'fs-extra' 7import { readFileSync } from 'fs-extra'
10import { VideoCommentModel } from '../models/video/video-comment'
11import { VideoAbuseModel } from '../models/video/video-abuse'
12import { VideoBlacklistModel } from '../models/video/video-blacklist'
13import { VideoImportModel } from '../models/video/video-import'
14import { ActorFollowModel } from '../models/activitypub/actor-follow'
15import { WEBSERVER } from '../initializers/constants' 8import { WEBSERVER } from '../initializers/constants'
9import { MCommentOwnerVideo, MVideo, MVideoAbuseVideo, MVideoAccountLight, MVideoBlacklistVideo } from '../typings/models/video'
10import { MActorFollowActors, MActorFollowFollowingFullFollowerAccount, MUser } from '../typings/models'
11import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import'
16 12
17type SendEmailOptions = { 13type SendEmailOptions = {
18 to: string[] 14 to: string[]
@@ -90,7 +86,7 @@ class Emailer {
90 } 86 }
91 } 87 }
92 88
93 addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) { 89 addNewVideoFromSubscriberNotification (to: string[], video: MVideoAccountLight) {
94 const channelName = video.VideoChannel.getDisplayName() 90 const channelName = video.VideoChannel.getDisplayName()
95 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 91 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
96 92
@@ -111,7 +107,7 @@ class Emailer {
111 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 107 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
112 } 108 }
113 109
114 addNewFollowNotification (to: string[], actorFollow: ActorFollowModel, followType: 'account' | 'channel') { 110 addNewFollowNotification (to: string[], actorFollow: MActorFollowFollowingFullFollowerAccount, followType: 'account' | 'channel') {
115 const followerName = actorFollow.ActorFollower.Account.getDisplayName() 111 const followerName = actorFollow.ActorFollower.Account.getDisplayName()
116 const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() 112 const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
117 113
@@ -130,7 +126,7 @@ class Emailer {
130 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 126 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
131 } 127 }
132 128
133 addNewInstanceFollowerNotification (to: string[], actorFollow: ActorFollowModel) { 129 addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) {
134 const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' 130 const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : ''
135 131
136 const text = `Hi dear admin,\n\n` + 132 const text = `Hi dear admin,\n\n` +
@@ -148,7 +144,7 @@ class Emailer {
148 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 144 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
149 } 145 }
150 146
151 myVideoPublishedNotification (to: string[], video: VideoModel) { 147 myVideoPublishedNotification (to: string[], video: MVideo) {
152 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 148 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
153 149
154 const text = `Hi dear user,\n\n` + 150 const text = `Hi dear user,\n\n` +
@@ -168,7 +164,7 @@ class Emailer {
168 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 164 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
169 } 165 }
170 166
171 myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) { 167 myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) {
172 const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() 168 const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
173 169
174 const text = `Hi dear user,\n\n` + 170 const text = `Hi dear user,\n\n` +
@@ -188,7 +184,7 @@ class Emailer {
188 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 184 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
189 } 185 }
190 186
191 myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) { 187 myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) {
192 const importUrl = WEBSERVER.URL + '/my-account/video-imports' 188 const importUrl = WEBSERVER.URL + '/my-account/video-imports'
193 189
194 const text = `Hi dear user,\n\n` + 190 const text = `Hi dear user,\n\n` +
@@ -208,7 +204,7 @@ class Emailer {
208 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 204 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
209 } 205 }
210 206
211 addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) { 207 addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) {
212 const accountName = comment.Account.getDisplayName() 208 const accountName = comment.Account.getDisplayName()
213 const video = comment.Video 209 const video = comment.Video
214 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() 210 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
@@ -230,7 +226,7 @@ class Emailer {
230 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 226 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
231 } 227 }
232 228
233 addNewCommentMentionNotification (to: string[], comment: VideoCommentModel) { 229 addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) {
234 const accountName = comment.Account.getDisplayName() 230 const accountName = comment.Account.getDisplayName()
235 const video = comment.Video 231 const video = comment.Video
236 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() 232 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
@@ -252,7 +248,7 @@ class Emailer {
252 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 248 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
253 } 249 }
254 250
255 addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) { 251 addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) {
256 const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() 252 const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath()
257 253
258 const text = `Hi,\n\n` + 254 const text = `Hi,\n\n` +
@@ -269,7 +265,7 @@ class Emailer {
269 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 265 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
270 } 266 }
271 267
272 addVideoAutoBlacklistModeratorsNotification (to: string[], video: VideoModel) { 268 addVideoAutoBlacklistModeratorsNotification (to: string[], video: MVideo) {
273 const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' 269 const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
274 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 270 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
275 271
@@ -292,7 +288,7 @@ class Emailer {
292 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 288 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
293 } 289 }
294 290
295 addNewUserRegistrationNotification (to: string[], user: UserModel) { 291 addNewUserRegistrationNotification (to: string[], user: MUser) {
296 const text = `Hi,\n\n` + 292 const text = `Hi,\n\n` +
297 `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + 293 `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` +
298 `Cheers,\n` + 294 `Cheers,\n` +
@@ -307,7 +303,7 @@ class Emailer {
307 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 303 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
308 } 304 }
309 305
310 addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) { 306 addVideoBlacklistNotification (to: string[], videoBlacklist: MVideoBlacklistVideo) {
311 const videoName = videoBlacklist.Video.name 307 const videoName = videoBlacklist.Video.name
312 const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() 308 const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
313 309
@@ -329,7 +325,7 @@ class Emailer {
329 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 325 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
330 } 326 }
331 327
332 addVideoUnblacklistNotification (to: string[], video: VideoModel) { 328 addVideoUnblacklistNotification (to: string[], video: MVideo) {
333 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 329 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
334 330
335 const text = 'Hi,\n\n' + 331 const text = 'Hi,\n\n' +
@@ -381,7 +377,7 @@ class Emailer {
381 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 377 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
382 } 378 }
383 379
384 addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) { 380 addUserBlockJob (user: MUser, blocked: boolean, reason?: string) {
385 const reasonString = reason ? ` for the following reason: ${reason}` : '' 381 const reasonString = reason ? ` for the following reason: ${reason}` : ''
386 const blockedWord = blocked ? 'blocked' : 'unblocked' 382 const blockedWord = blocked ? 'blocked' : 'unblocked'
387 const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` 383 const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.`
diff --git a/server/lib/hls.ts b/server/lib/hls.ts
index 98da4dcd8..05136c21c 100644
--- a/server/lib/hls.ts
+++ b/server/lib/hls.ts
@@ -1,4 +1,3 @@
1import { VideoModel } from '../models/video/video'
2import { basename, dirname, join } from 'path' 1import { basename, dirname, join } from 'path'
3import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants' 2import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants'
4import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra' 3import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra'
@@ -12,6 +11,7 @@ import { flatten, uniq } from 'lodash'
12import { VideoFileModel } from '../models/video/video-file' 11import { VideoFileModel } from '../models/video/video-file'
13import { CONFIG } from '../initializers/config' 12import { CONFIG } from '../initializers/config'
14import { sequelizeTypescript } from '../initializers/database' 13import { sequelizeTypescript } from '../initializers/database'
14import { MVideoWithFile } from '@server/typings/models'
15 15
16async function updateStreamingPlaylistsInfohashesIfNeeded () { 16async function updateStreamingPlaylistsInfohashesIfNeeded () {
17 const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion() 17 const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion()
@@ -28,7 +28,7 @@ async function updateStreamingPlaylistsInfohashesIfNeeded () {
28 } 28 }
29} 29}
30 30
31async function updateMasterHLSPlaylist (video: VideoModel) { 31async function updateMasterHLSPlaylist (video: MVideoWithFile) {
32 const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) 32 const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
33 const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ] 33 const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ]
34 const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) 34 const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename())
@@ -55,7 +55,7 @@ async function updateMasterHLSPlaylist (video: VideoModel) {
55 await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n') 55 await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n')
56} 56}
57 57
58async function updateSha256Segments (video: VideoModel) { 58async function updateSha256Segments (video: MVideoWithFile) {
59 const json: { [filename: string]: { [range: string]: string } } = {} 59 const json: { [filename: string]: { [range: string]: string } } = {}
60 60
61 const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) 61 const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
diff --git a/server/lib/job-queue/handlers/activitypub-follow.ts b/server/lib/job-queue/handlers/activitypub-follow.ts
index 4ae66cd01..5cb55cad6 100644
--- a/server/lib/job-queue/handlers/activitypub-follow.ts
+++ b/server/lib/job-queue/handlers/activitypub-follow.ts
@@ -10,6 +10,7 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
10import { ActorModel } from '../../../models/activitypub/actor' 10import { ActorModel } from '../../../models/activitypub/actor'
11import { Notifier } from '../../notifier' 11import { Notifier } from '../../notifier'
12import { sequelizeTypescript } from '../../../initializers/database' 12import { sequelizeTypescript } from '../../../initializers/database'
13import { MAccount, MActor, MActorFollowActors, MActorFollowFull, MActorFull } from '../../../typings/models'
13 14
14export type ActivitypubFollowPayload = { 15export type ActivitypubFollowPayload = {
15 followerActorId: number 16 followerActorId: number
@@ -23,13 +24,13 @@ async function processActivityPubFollow (job: Bull.Job) {
23 24
24 logger.info('Processing ActivityPub follow in job %d.', job.id) 25 logger.info('Processing ActivityPub follow in job %d.', job.id)
25 26
26 let targetActor: ActorModel 27 let targetActor: MActorFull
27 if (!host || host === WEBSERVER.HOST) { 28 if (!host || host === WEBSERVER.HOST) {
28 targetActor = await ActorModel.loadLocalByName(payload.name) 29 targetActor = await ActorModel.loadLocalByName(payload.name)
29 } else { 30 } else {
30 const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) 31 const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP)
31 const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost) 32 const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost)
32 targetActor = await getOrCreateActorAndServerAndModel(actorUrl) 33 targetActor = await getOrCreateActorAndServerAndModel(actorUrl, 'all')
33 } 34 }
34 35
35 const fromActor = await ActorModel.load(payload.followerActorId) 36 const fromActor = await ActorModel.load(payload.followerActorId)
@@ -44,7 +45,7 @@ export {
44 45
45// --------------------------------------------------------------------------- 46// ---------------------------------------------------------------------------
46 47
47async function follow (fromActor: ActorModel, targetActor: ActorModel) { 48async function follow (fromActor: MActor, targetActor: MActorFull) {
48 if (fromActor.id === targetActor.id) { 49 if (fromActor.id === targetActor.id) {
49 throw new Error('Follower is the same than target actor.') 50 throw new Error('Follower is the same than target actor.')
50 } 51 }
@@ -53,7 +54,7 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) {
53 const state = !fromActor.serverId && !targetActor.serverId ? 'accepted' : 'pending' 54 const state = !fromActor.serverId && !targetActor.serverId ? 'accepted' : 'pending'
54 55
55 const actorFollow = await sequelizeTypescript.transaction(async t => { 56 const actorFollow = await sequelizeTypescript.transaction(async t => {
56 const [ actorFollow ] = await ActorFollowModel.findOrCreate({ 57 const [ actorFollow ] = await ActorFollowModel.findOrCreate<MActorFollowActors>({
57 where: { 58 where: {
58 actorId: fromActor.id, 59 actorId: fromActor.id,
59 targetActorId: targetActor.id 60 targetActorId: targetActor.id
@@ -74,5 +75,14 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) {
74 return actorFollow 75 return actorFollow
75 }) 76 })
76 77
77 if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewUserFollow(actorFollow) 78 if (actorFollow.state === 'accepted') {
79 const followerFull = Object.assign(fromActor, { Account: await actorFollow.ActorFollower.$get('Account') as MAccount })
80
81 const actorFollowFull = Object.assign(actorFollow, {
82 ActorFollowing: targetActor,
83 ActorFollower: followerFull
84 })
85
86 Notifier.Instance.notifyOfNewUserFollow(actorFollowFull)
87 }
78} 88}
diff --git a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts
index c3f59dc77..0182c5169 100644
--- a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts
+++ b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts
@@ -11,6 +11,7 @@ import { AccountModel } from '../../../models/account/account'
11import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 11import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
12import { VideoShareModel } from '../../../models/video/video-share' 12import { VideoShareModel } from '../../../models/video/video-share'
13import { VideoCommentModel } from '../../../models/video/video-comment' 13import { VideoCommentModel } from '../../../models/video/video-comment'
14import { MAccountDefault, MVideoFullLight } from '../../../typings/models'
14 15
15type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists' 16type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists'
16 17
@@ -26,10 +27,10 @@ async function processActivityPubHttpFetcher (job: Bull.Job) {
26 27
27 const payload = job.data as ActivitypubHttpFetcherPayload 28 const payload = job.data as ActivitypubHttpFetcherPayload
28 29
29 let video: VideoModel 30 let video: MVideoFullLight
30 if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) 31 if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId)
31 32
32 let account: AccountModel 33 let account: MAccountDefault
33 if (payload.accountId) account = await AccountModel.load(payload.accountId) 34 if (payload.accountId) account = await AccountModel.load(payload.accountId)
34 35
35 const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = { 36 const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = {
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 cdee1f6fd..d3bde6e6a 100644
--- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
+++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
@@ -3,6 +3,7 @@ import { getServerActor } from '../../../../helpers/utils'
3import { ActorModel } from '../../../../models/activitypub/actor' 3import { ActorModel } from '../../../../models/activitypub/actor'
4import { sha256 } from '../../../../helpers/core-utils' 4import { sha256 } from '../../../../helpers/core-utils'
5import { HTTP_SIGNATURE } from '../../../../initializers/constants' 5import { HTTP_SIGNATURE } from '../../../../initializers/constants'
6import { MActor } from '../../../../typings/models'
6 7
7type Payload = { body: any, signatureActorId?: number } 8type Payload = { body: any, signatureActorId?: number }
8 9
@@ -19,7 +20,8 @@ async function computeBody (payload: Payload) {
19} 20}
20 21
21async function buildSignedRequestOptions (payload: Payload) { 22async function buildSignedRequestOptions (payload: Payload) {
22 let actor: ActorModel | null 23 let actor: MActor | null
24
23 if (payload.signatureActorId) { 25 if (payload.signatureActorId) {
24 actor = await ActorModel.load(payload.signatureActorId) 26 actor = await ActorModel.load(payload.signatureActorId)
25 if (!actor) throw new Error('Unknown signature actor id.') 27 if (!actor) throw new Error('Unknown signature actor id.')
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts
index 8cacb0ef3..5c5b7dccb 100644
--- a/server/lib/job-queue/handlers/video-file-import.ts
+++ b/server/lib/job-queue/handlers/video-file-import.ts
@@ -6,6 +6,7 @@ import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg
6import { copy, stat } from 'fs-extra' 6import { copy, stat } from 'fs-extra'
7import { VideoFileModel } from '../../../models/video/video-file' 7import { VideoFileModel } from '../../../models/video/video-file'
8import { extname } from 'path' 8import { extname } from 'path'
9import { MVideoFile, MVideoWithFile } from '@server/typings/models'
9 10
10export type VideoFileImportPayload = { 11export type VideoFileImportPayload = {
11 videoUUID: string, 12 videoUUID: string,
@@ -37,7 +38,7 @@ export {
37 38
38// --------------------------------------------------------------------------- 39// ---------------------------------------------------------------------------
39 40
40async function updateVideoFile (video: VideoModel, inputFilePath: string) { 41async function updateVideoFile (video: MVideoWithFile, inputFilePath: string) {
41 const { videoFileResolution } = await getVideoFileResolution(inputFilePath) 42 const { videoFileResolution } = await getVideoFileResolution(inputFilePath)
42 const { size } = await stat(inputFilePath) 43 const { size } = await stat(inputFilePath)
43 const fps = await getVideoFileFPS(inputFilePath) 44 const fps = await getVideoFileFPS(inputFilePath)
@@ -48,7 +49,7 @@ async function updateVideoFile (video: VideoModel, inputFilePath: string) {
48 size, 49 size,
49 fps, 50 fps,
50 videoId: video.id 51 videoId: video.id
51 }) 52 }) as MVideoFile
52 53
53 const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution) 54 const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution)
54 55
@@ -60,9 +61,9 @@ async function updateVideoFile (video: VideoModel, inputFilePath: string) {
60 video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) 61 video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile)
61 62
62 // Update the database 63 // Update the database
63 currentVideoFile.set('extname', updatedVideoFile.extname) 64 currentVideoFile.extname = updatedVideoFile.extname
64 currentVideoFile.set('size', updatedVideoFile.size) 65 currentVideoFile.size = updatedVideoFile.size
65 currentVideoFile.set('fps', updatedVideoFile.fps) 66 currentVideoFile.fps = updatedVideoFile.fps
66 67
67 updatedVideoFile = currentVideoFile 68 updatedVideoFile = currentVideoFile
68 } 69 }
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index 13b741180..ff8c93328 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -17,9 +17,10 @@ import { move, remove, stat } from 'fs-extra'
17import { Notifier } from '../../notifier' 17import { Notifier } from '../../notifier'
18import { CONFIG } from '../../../initializers/config' 18import { CONFIG } from '../../../initializers/config'
19import { sequelizeTypescript } from '../../../initializers/database' 19import { sequelizeTypescript } from '../../../initializers/database'
20import { ThumbnailModel } from '../../../models/video/thumbnail'
21import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail' 20import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail'
22import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' 21import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
22import { MThumbnail } from '../../../typings/models/video/thumbnail'
23import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/typings/models/video/video-import'
23 24
24type VideoImportYoutubeDLPayload = { 25type VideoImportYoutubeDLPayload = {
25 type: 'youtube-dl' 26 type: 'youtube-dl'
@@ -110,7 +111,7 @@ type ProcessFileOptions = {
110 generateThumbnail: boolean 111 generateThumbnail: boolean
111 generatePreview: boolean 112 generatePreview: boolean
112} 113}
113async function processFile (downloader: () => Promise<string>, videoImport: VideoImportModel, options: ProcessFileOptions) { 114async function processFile (downloader: () => Promise<string>, videoImport: MVideoImportDefault, options: ProcessFileOptions) {
114 let tempVideoPath: string 115 let tempVideoPath: string
115 let videoDestFile: string 116 let videoDestFile: string
116 let videoFile: VideoFileModel 117 let videoFile: VideoFileModel
@@ -139,41 +140,44 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
139 videoId: videoImport.videoId 140 videoId: videoImport.videoId
140 } 141 }
141 videoFile = new VideoFileModel(videoFileData) 142 videoFile = new VideoFileModel(videoFileData)
143
144 const videoWithFiles = Object.assign(videoImport.Video, { VideoFiles: [ videoFile ] })
142 // To clean files if the import fails 145 // To clean files if the import fails
143 videoImport.Video.VideoFiles = [ videoFile ] 146 const videoImportWithFiles: MVideoImportDefaultFiles = Object.assign(videoImport, { Video: videoWithFiles })
144 147
145 // Move file 148 // Move file
146 videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImport.Video.getVideoFilename(videoFile)) 149 videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImportWithFiles.Video.getVideoFilename(videoFile))
147 await move(tempVideoPath, videoDestFile) 150 await move(tempVideoPath, videoDestFile)
148 tempVideoPath = null // This path is not used anymore 151 tempVideoPath = null // This path is not used anymore
149 152
150 // Process thumbnail 153 // Process thumbnail
151 let thumbnailModel: ThumbnailModel 154 let thumbnailModel: MThumbnail
152 if (options.downloadThumbnail && options.thumbnailUrl) { 155 if (options.downloadThumbnail && options.thumbnailUrl) {
153 thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.MINIATURE) 156 thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.MINIATURE)
154 } else if (options.generateThumbnail || options.downloadThumbnail) { 157 } else if (options.generateThumbnail || options.downloadThumbnail) {
155 thumbnailModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.MINIATURE) 158 thumbnailModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.MINIATURE)
156 } 159 }
157 160
158 // Process preview 161 // Process preview
159 let previewModel: ThumbnailModel 162 let previewModel: MThumbnail
160 if (options.downloadPreview && options.thumbnailUrl) { 163 if (options.downloadPreview && options.thumbnailUrl) {
161 previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.PREVIEW) 164 previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.PREVIEW)
162 } else if (options.generatePreview || options.downloadPreview) { 165 } else if (options.generatePreview || options.downloadPreview) {
163 previewModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.PREVIEW) 166 previewModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.PREVIEW)
164 } 167 }
165 168
166 // Create torrent 169 // Create torrent
167 await videoImport.Video.createTorrentAndSetInfoHash(videoFile) 170 await videoImportWithFiles.Video.createTorrentAndSetInfoHash(videoFile)
171
172 const { videoImportUpdated, video } = await sequelizeTypescript.transaction(async t => {
173 const videoImportToUpdate = videoImportWithFiles as MVideoImportVideo
168 174
169 const videoImportUpdated: VideoImportModel = await sequelizeTypescript.transaction(async t => {
170 // Refresh video 175 // Refresh video
171 const video = await VideoModel.load(videoImport.videoId, t) 176 const video = await VideoModel.load(videoImportToUpdate.videoId, t)
172 if (!video) throw new Error('Video linked to import ' + videoImport.videoId + ' does not exist anymore.') 177 if (!video) throw new Error('Video linked to import ' + videoImportToUpdate.videoId + ' does not exist anymore.')
173 videoImport.Video = video
174 178
175 const videoFileCreated = await videoFile.save({ transaction: t }) 179 const videoFileCreated = await videoFile.save({ transaction: t })
176 video.VideoFiles = [ videoFileCreated ] 180 videoImportToUpdate.Video = Object.assign(video, { VideoFiles: [ videoFileCreated ] })
177 181
178 // Update video DB object 182 // Update video DB object
179 video.duration = duration 183 video.duration = duration
@@ -188,25 +192,25 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
188 await federateVideoIfNeeded(videoForFederation, true, t) 192 await federateVideoIfNeeded(videoForFederation, true, t)
189 193
190 // Update video import object 194 // Update video import object
191 videoImport.state = VideoImportState.SUCCESS 195 videoImportToUpdate.state = VideoImportState.SUCCESS
192 const videoImportUpdated = await videoImport.save({ transaction: t }) 196 const videoImportUpdated = await videoImportToUpdate.save({ transaction: t }) as MVideoImportVideo
197 videoImportUpdated.Video = video
193 198
194 logger.info('Video %s imported.', video.uuid) 199 logger.info('Video %s imported.', video.uuid)
195 200
196 videoImportUpdated.Video = videoForFederation 201 return { videoImportUpdated, video: videoForFederation }
197 return videoImportUpdated
198 }) 202 })
199 203
200 Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true) 204 Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
201 205
202 if (videoImportUpdated.Video.isBlacklisted()) { 206 if (video.isBlacklisted()) {
203 Notifier.Instance.notifyOnVideoAutoBlacklist(videoImportUpdated.Video) 207 Notifier.Instance.notifyOnVideoAutoBlacklist(video)
204 } else { 208 } else {
205 Notifier.Instance.notifyOnNewVideoIfNeeded(videoImportUpdated.Video) 209 Notifier.Instance.notifyOnNewVideoIfNeeded(video)
206 } 210 }
207 211
208 // Create transcoding jobs? 212 // Create transcoding jobs?
209 if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { 213 if (video.state === VideoState.TO_TRANSCODE) {
210 // Put uuid because we don't have id auto incremented for now 214 // Put uuid because we don't have id auto incremented for now
211 const dataInput = { 215 const dataInput = {
212 type: 'optimize' as 'optimize', 216 type: 'optimize' as 'optimize',
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts
index 981daf9a1..2ebe15bcb 100644
--- a/server/lib/job-queue/handlers/video-transcoding.ts
+++ b/server/lib/job-queue/handlers/video-transcoding.ts
@@ -11,6 +11,7 @@ import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils'
11import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding' 11import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding'
12import { Notifier } from '../../notifier' 12import { Notifier } from '../../notifier'
13import { CONFIG } from '../../../initializers/config' 13import { CONFIG } from '../../../initializers/config'
14import { MVideoUUID, MVideoWithFile } from '@server/typings/models'
14 15
15interface BaseTranscodingPayload { 16interface BaseTranscodingPayload {
16 videoUUID: string 17 videoUUID: string
@@ -73,7 +74,7 @@ async function processVideoTranscoding (job: Bull.Job) {
73 return video 74 return video
74} 75}
75 76
76async function onHlsPlaylistGenerationSuccess (video: VideoModel) { 77async function onHlsPlaylistGenerationSuccess (video: MVideoUUID) {
77 if (video === undefined) return undefined 78 if (video === undefined) return undefined
78 79
79 await sequelizeTypescript.transaction(async t => { 80 await sequelizeTypescript.transaction(async t => {
@@ -87,7 +88,7 @@ async function onHlsPlaylistGenerationSuccess (video: VideoModel) {
87 }) 88 })
88} 89}
89 90
90async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) { 91async function publishNewResolutionIfNeeded (video: MVideoUUID, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) {
91 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { 92 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
92 // Maybe the video changed in database, refresh it 93 // Maybe the video changed in database, refresh it
93 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) 94 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
@@ -119,7 +120,7 @@ async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewRes
119 await createHlsJobIfEnabled(payload) 120 await createHlsJobIfEnabled(payload)
120} 121}
121 122
122async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: OptimizeTranscodingPayload) { 123async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: OptimizeTranscodingPayload) {
123 if (videoArg === undefined) return undefined 124 if (videoArg === undefined) return undefined
124 125
125 // Outside the transaction (IO on disk) 126 // Outside the transaction (IO on disk)
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts
index a7dfb0979..23f76a21a 100644
--- a/server/lib/notifier.ts
+++ b/server/lib/notifier.ts
@@ -8,13 +8,23 @@ import { UserModel } from '../models/account/user'
8import { PeerTubeSocket } from './peertube-socket' 8import { PeerTubeSocket } from './peertube-socket'
9import { CONFIG } from '../initializers/config' 9import { CONFIG } from '../initializers/config'
10import { VideoPrivacy, VideoState } from '../../shared/models/videos' 10import { VideoPrivacy, VideoState } from '../../shared/models/videos'
11import { VideoAbuseModel } from '../models/video/video-abuse'
12import { VideoBlacklistModel } from '../models/video/video-blacklist' 11import { VideoBlacklistModel } from '../models/video/video-blacklist'
13import * as Bluebird from 'bluebird' 12import * as Bluebird from 'bluebird'
14import { VideoImportModel } from '../models/video/video-import' 13import { VideoImportModel } from '../models/video/video-import'
15import { AccountBlocklistModel } from '../models/account/account-blocklist' 14import { AccountBlocklistModel } from '../models/account/account-blocklist'
15import {
16 MCommentOwnerVideo,
17 MVideo,
18 MVideoAbuseVideo,
19 MVideoAccountLight,
20 MVideoBlacklistVideo,
21 MVideoFullLight
22} from '../typings/models/video'
23import { MUser, MUserAccount, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/typings/models/user'
24import { MActorFollowActors, MActorFollowFull, MActorFollowFollowingFullFollowerAccount } from '../typings/models'
16import { ActorFollowModel } from '../models/activitypub/actor-follow' 25import { ActorFollowModel } from '../models/activitypub/actor-follow'
17import { AccountModel } from '../models/account/account' 26import { MVideoImportVideo } from '@server/typings/models/video/video-import'
27import { AccountModel } from '@server/models/account/account'
18 28
19class Notifier { 29class Notifier {
20 30
@@ -22,7 +32,7 @@ class Notifier {
22 32
23 private constructor () {} 33 private constructor () {}
24 34
25 notifyOnNewVideoIfNeeded (video: VideoModel): void { 35 notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void {
26 // Only notify on public and published videos which are not blacklisted 36 // Only notify on public and published videos which are not blacklisted
27 if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return 37 if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return
28 38
@@ -30,7 +40,7 @@ class Notifier {
30 .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) 40 .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
31 } 41 }
32 42
33 notifyOnVideoPublishedAfterTranscoding (video: VideoModel): void { 43 notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void {
34 // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update 44 // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update
35 if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return 45 if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return
36 46
@@ -38,7 +48,7 @@ class Notifier {
38 .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err })) 48 .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err }))
39 } 49 }
40 50
41 notifyOnVideoPublishedAfterScheduledUpdate (video: VideoModel): void { 51 notifyOnVideoPublishedAfterScheduledUpdate (video: MVideoFullLight): void {
42 // don't notify if video is still blacklisted or waiting for transcoding 52 // don't notify if video is still blacklisted or waiting for transcoding
43 if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return 53 if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
44 54
@@ -46,7 +56,7 @@ class Notifier {
46 .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err })) 56 .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err }))
47 } 57 }
48 58
49 notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: VideoModel): void { 59 notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: MVideoFullLight): void {
50 // don't notify if video is still waiting for transcoding or scheduled update 60 // don't notify if video is still waiting for transcoding or scheduled update
51 if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return 61 if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
52 62
@@ -54,7 +64,7 @@ class Notifier {
54 .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length 64 .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length
55 } 65 }
56 66
57 notifyOnNewComment (comment: VideoCommentModel): void { 67 notifyOnNewComment (comment: MCommentOwnerVideo): void {
58 this.notifyVideoOwnerOfNewComment(comment) 68 this.notifyVideoOwnerOfNewComment(comment)
59 .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err })) 69 .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err }))
60 70
@@ -62,37 +72,37 @@ class Notifier {
62 .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) 72 .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err }))
63 } 73 }
64 74
65 notifyOnNewVideoAbuse (videoAbuse: VideoAbuseModel): void { 75 notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void {
66 this.notifyModeratorsOfNewVideoAbuse(videoAbuse) 76 this.notifyModeratorsOfNewVideoAbuse(videoAbuse)
67 .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) 77 .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
68 } 78 }
69 79
70 notifyOnVideoAutoBlacklist (video: VideoModel): void { 80 notifyOnVideoAutoBlacklist (video: MVideo): void {
71 this.notifyModeratorsOfVideoAutoBlacklist(video) 81 this.notifyModeratorsOfVideoAutoBlacklist(video)
72 .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err })) 82 .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err }))
73 } 83 }
74 84
75 notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void { 85 notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void {
76 this.notifyVideoOwnerOfBlacklist(videoBlacklist) 86 this.notifyVideoOwnerOfBlacklist(videoBlacklist)
77 .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) 87 .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
78 } 88 }
79 89
80 notifyOnVideoUnblacklist (video: VideoModel): void { 90 notifyOnVideoUnblacklist (video: MVideo): void {
81 this.notifyVideoOwnerOfUnblacklist(video) 91 this.notifyVideoOwnerOfUnblacklist(video)
82 .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err })) 92 .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err }))
83 } 93 }
84 94
85 notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void { 95 notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void {
86 this.notifyOwnerVideoImportIsFinished(videoImport, success) 96 this.notifyOwnerVideoImportIsFinished(videoImport, success)
87 .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) 97 .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
88 } 98 }
89 99
90 notifyOnNewUserRegistration (user: UserModel): void { 100 notifyOnNewUserRegistration (user: MUserAccount): void {
91 this.notifyModeratorsOfNewUserRegistration(user) 101 this.notifyModeratorsOfNewUserRegistration(user)
92 .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err })) 102 .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
93 } 103 }
94 104
95 notifyOfNewUserFollow (actorFollow: ActorFollowModel): void { 105 notifyOfNewUserFollow (actorFollow: MActorFollowFollowingFullFollowerAccount): void {
96 this.notifyUserOfNewActorFollow(actorFollow) 106 this.notifyUserOfNewActorFollow(actorFollow)
97 .catch(err => { 107 .catch(err => {
98 logger.error( 108 logger.error(
@@ -104,14 +114,14 @@ class Notifier {
104 }) 114 })
105 } 115 }
106 116
107 notifyOfNewInstanceFollow (actorFollow: ActorFollowModel): void { 117 notifyOfNewInstanceFollow (actorFollow: MActorFollowActors): void {
108 this.notifyAdminsOfNewInstanceFollow(actorFollow) 118 this.notifyAdminsOfNewInstanceFollow(actorFollow)
109 .catch(err => { 119 .catch(err => {
110 logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err }) 120 logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err })
111 }) 121 })
112 } 122 }
113 123
114 private async notifySubscribersOfNewVideo (video: VideoModel) { 124 private async notifySubscribersOfNewVideo (video: MVideoAccountLight) {
115 // List all followers that are users 125 // List all followers that are users
116 const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) 126 const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
117 127
@@ -127,7 +137,7 @@ class Notifier {
127 userId: user.id, 137 userId: user.id,
128 videoId: video.id 138 videoId: video.id
129 }) 139 })
130 notification.Video = video 140 notification.Video = video as VideoModel
131 141
132 return notification 142 return notification
133 } 143 }
@@ -139,7 +149,7 @@ class Notifier {
139 return this.notify({ users, settingGetter, notificationCreator, emailSender }) 149 return this.notify({ users, settingGetter, notificationCreator, emailSender })
140 } 150 }
141 151
142 private async notifyVideoOwnerOfNewComment (comment: VideoCommentModel) { 152 private async notifyVideoOwnerOfNewComment (comment: MCommentOwnerVideo) {
143 if (comment.Video.isOwned() === false) return 153 if (comment.Video.isOwned() === false) return
144 154
145 const user = await UserModel.loadByVideoId(comment.videoId) 155 const user = await UserModel.loadByVideoId(comment.videoId)
@@ -162,7 +172,7 @@ class Notifier {
162 userId: user.id, 172 userId: user.id,
163 commentId: comment.id 173 commentId: comment.id
164 }) 174 })
165 notification.Comment = comment 175 notification.Comment = comment as VideoCommentModel
166 176
167 return notification 177 return notification
168 } 178 }
@@ -174,7 +184,7 @@ class Notifier {
174 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 184 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
175 } 185 }
176 186
177 private async notifyOfCommentMention (comment: VideoCommentModel) { 187 private async notifyOfCommentMention (comment: MCommentOwnerVideo) {
178 const extractedUsernames = comment.extractMentions() 188 const extractedUsernames = comment.extractMentions()
179 logger.debug( 189 logger.debug(
180 'Extracted %d username from comment %s.', extractedUsernames.length, comment.url, 190 'Extracted %d username from comment %s.', extractedUsernames.length, comment.url,
@@ -209,7 +219,7 @@ class Notifier {
209 userId: user.id, 219 userId: user.id,
210 commentId: comment.id 220 commentId: comment.id
211 }) 221 })
212 notification.Comment = comment 222 notification.Comment = comment as VideoCommentModel
213 223
214 return notification 224 return notification
215 } 225 }
@@ -221,7 +231,7 @@ class Notifier {
221 return this.notify({ users, settingGetter, notificationCreator, emailSender }) 231 return this.notify({ users, settingGetter, notificationCreator, emailSender })
222 } 232 }
223 233
224 private async notifyUserOfNewActorFollow (actorFollow: ActorFollowModel) { 234 private async notifyUserOfNewActorFollow (actorFollow: MActorFollowFollowingFullFollowerAccount) {
225 if (actorFollow.ActorFollowing.isOwned() === false) return 235 if (actorFollow.ActorFollowing.isOwned() === false) return
226 236
227 // Account follows one of our account? 237 // Account follows one of our account?
@@ -236,9 +246,6 @@ class Notifier {
236 246
237 if (!user) return 247 if (!user) return
238 248
239 if (!actorFollow.ActorFollower.Account || !actorFollow.ActorFollower.Account.name) {
240 actorFollow.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as AccountModel
241 }
242 const followerAccount = actorFollow.ActorFollower.Account 249 const followerAccount = actorFollow.ActorFollower.Account
243 250
244 const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id) 251 const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id)
@@ -256,7 +263,7 @@ class Notifier {
256 userId: user.id, 263 userId: user.id,
257 actorFollowId: actorFollow.id 264 actorFollowId: actorFollow.id
258 }) 265 })
259 notification.ActorFollow = actorFollow 266 notification.ActorFollow = actorFollow as ActorFollowModel
260 267
261 return notification 268 return notification
262 } 269 }
@@ -268,7 +275,7 @@ class Notifier {
268 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 275 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
269 } 276 }
270 277
271 private async notifyAdminsOfNewInstanceFollow (actorFollow: ActorFollowModel) { 278 private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowActors) {
272 const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW) 279 const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
273 280
274 logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url) 281 logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url)
@@ -283,7 +290,7 @@ class Notifier {
283 userId: user.id, 290 userId: user.id,
284 actorFollowId: actorFollow.id 291 actorFollowId: actorFollow.id
285 }) 292 })
286 notification.ActorFollow = actorFollow 293 notification.ActorFollow = actorFollow as ActorFollowModel
287 294
288 return notification 295 return notification
289 } 296 }
@@ -295,7 +302,7 @@ class Notifier {
295 return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) 302 return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
296 } 303 }
297 304
298 private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) { 305 private async notifyModeratorsOfNewVideoAbuse (videoAbuse: MVideoAbuseVideo) {
299 const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) 306 const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES)
300 if (moderators.length === 0) return 307 if (moderators.length === 0) return
301 308
@@ -306,7 +313,7 @@ class Notifier {
306 } 313 }
307 314
308 async function notificationCreator (user: UserModel) { 315 async function notificationCreator (user: UserModel) {
309 const notification = await UserNotificationModel.create({ 316 const notification: UserNotificationModelForApi = await UserNotificationModel.create({
310 type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, 317 type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS,
311 userId: user.id, 318 userId: user.id,
312 videoAbuseId: videoAbuse.id 319 videoAbuseId: videoAbuse.id
@@ -323,7 +330,7 @@ class Notifier {
323 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) 330 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
324 } 331 }
325 332
326 private async notifyModeratorsOfVideoAutoBlacklist (video: VideoModel) { 333 private async notifyModeratorsOfVideoAutoBlacklist (video: MVideo) {
327 const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST) 334 const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST)
328 if (moderators.length === 0) return 335 if (moderators.length === 0) return
329 336
@@ -339,7 +346,7 @@ class Notifier {
339 userId: user.id, 346 userId: user.id,
340 videoId: video.id 347 videoId: video.id
341 }) 348 })
342 notification.Video = video 349 notification.Video = video as VideoModel
343 350
344 return notification 351 return notification
345 } 352 }
@@ -351,7 +358,7 @@ class Notifier {
351 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) 358 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
352 } 359 }
353 360
354 private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) { 361 private async notifyVideoOwnerOfBlacklist (videoBlacklist: MVideoBlacklistVideo) {
355 const user = await UserModel.loadByVideoId(videoBlacklist.videoId) 362 const user = await UserModel.loadByVideoId(videoBlacklist.videoId)
356 if (!user) return 363 if (!user) return
357 364
@@ -367,7 +374,7 @@ class Notifier {
367 userId: user.id, 374 userId: user.id,
368 videoBlacklistId: videoBlacklist.id 375 videoBlacklistId: videoBlacklist.id
369 }) 376 })
370 notification.VideoBlacklist = videoBlacklist 377 notification.VideoBlacklist = videoBlacklist as VideoBlacklistModel
371 378
372 return notification 379 return notification
373 } 380 }
@@ -379,7 +386,7 @@ class Notifier {
379 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 386 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
380 } 387 }
381 388
382 private async notifyVideoOwnerOfUnblacklist (video: VideoModel) { 389 private async notifyVideoOwnerOfUnblacklist (video: MVideo) {
383 const user = await UserModel.loadByVideoId(video.id) 390 const user = await UserModel.loadByVideoId(video.id)
384 if (!user) return 391 if (!user) return
385 392
@@ -395,7 +402,7 @@ class Notifier {
395 userId: user.id, 402 userId: user.id,
396 videoId: video.id 403 videoId: video.id
397 }) 404 })
398 notification.Video = video 405 notification.Video = video as VideoModel
399 406
400 return notification 407 return notification
401 } 408 }
@@ -407,7 +414,7 @@ class Notifier {
407 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 414 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
408 } 415 }
409 416
410 private async notifyOwnedVideoHasBeenPublished (video: VideoModel) { 417 private async notifyOwnedVideoHasBeenPublished (video: MVideoFullLight) {
411 const user = await UserModel.loadByVideoId(video.id) 418 const user = await UserModel.loadByVideoId(video.id)
412 if (!user) return 419 if (!user) return
413 420
@@ -423,7 +430,7 @@ class Notifier {
423 userId: user.id, 430 userId: user.id,
424 videoId: video.id 431 videoId: video.id
425 }) 432 })
426 notification.Video = video 433 notification.Video = video as VideoModel
427 434
428 return notification 435 return notification
429 } 436 }
@@ -435,7 +442,7 @@ class Notifier {
435 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 442 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
436 } 443 }
437 444
438 private async notifyOwnerVideoImportIsFinished (videoImport: VideoImportModel, success: boolean) { 445 private async notifyOwnerVideoImportIsFinished (videoImport: MVideoImportVideo, success: boolean) {
439 const user = await UserModel.loadByVideoImportId(videoImport.id) 446 const user = await UserModel.loadByVideoImportId(videoImport.id)
440 if (!user) return 447 if (!user) return
441 448
@@ -451,7 +458,7 @@ class Notifier {
451 userId: user.id, 458 userId: user.id,
452 videoImportId: videoImport.id 459 videoImportId: videoImport.id
453 }) 460 })
454 notification.VideoImport = videoImport 461 notification.VideoImport = videoImport as VideoImportModel
455 462
456 return notification 463 return notification
457 } 464 }
@@ -465,13 +472,13 @@ class Notifier {
465 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 472 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
466 } 473 }
467 474
468 private async notifyModeratorsOfNewUserRegistration (registeredUser: UserModel) { 475 private async notifyModeratorsOfNewUserRegistration (registeredUser: MUserAccount) {
469 const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS) 476 const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS)
470 if (moderators.length === 0) return 477 if (moderators.length === 0) return
471 478
472 logger.info( 479 logger.info(
473 'Notifying %s moderators of new user registration of %s.', 480 'Notifying %s moderators of new user registration of %s.',
474 moderators.length, registeredUser.Account.Actor.preferredUsername 481 moderators.length, registeredUser.username
475 ) 482 )
476 483
477 function settingGetter (user: UserModel) { 484 function settingGetter (user: UserModel) {
@@ -484,7 +491,7 @@ class Notifier {
484 userId: user.id, 491 userId: user.id,
485 accountId: registeredUser.Account.id 492 accountId: registeredUser.Account.id
486 }) 493 })
487 notification.Account = registeredUser.Account 494 notification.Account = registeredUser.Account as AccountModel
488 495
489 return notification 496 return notification
490 } 497 }
@@ -497,10 +504,10 @@ class Notifier {
497 } 504 }
498 505
499 private async notify (options: { 506 private async notify (options: {
500 users: UserModel[], 507 users: MUserWithNotificationSetting[],
501 notificationCreator: (user: UserModel) => Promise<UserNotificationModel>, 508 notificationCreator: (user: MUserWithNotificationSetting) => Promise<UserNotificationModelForApi>,
502 emailSender: (emails: string[]) => Promise<any> | Bluebird<any>, 509 emailSender: (emails: string[]) => Promise<any> | Bluebird<any>,
503 settingGetter: (user: UserModel) => UserNotificationSettingValue 510 settingGetter: (user: MUserWithNotificationSetting) => UserNotificationSettingValue
504 }) { 511 }) {
505 const emails: string[] = [] 512 const emails: string[] = []
506 513
@@ -521,7 +528,7 @@ class Notifier {
521 } 528 }
522 } 529 }
523 530
524 private isEmailEnabled (user: UserModel, value: UserNotificationSettingValue) { 531 private isEmailEnabled (user: MUser, value: UserNotificationSettingValue) {
525 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false 532 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false
526 533
527 return value & UserNotificationSettingValue.EMAIL 534 return value & UserNotificationSettingValue.EMAIL
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts
index a1153e88a..086856f41 100644
--- a/server/lib/oauth-model.ts
+++ b/server/lib/oauth-model.ts
@@ -8,10 +8,11 @@ import { LRU_CACHE } from '../initializers/constants'
8import { Transaction } from 'sequelize' 8import { Transaction } from 'sequelize'
9import { CONFIG } from '../initializers/config' 9import { CONFIG } from '../initializers/config'
10import * as LRUCache from 'lru-cache' 10import * as LRUCache from 'lru-cache'
11import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
11 12
12type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } 13type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date }
13 14
14const accessTokenCache = new LRUCache<string, OAuthTokenModel>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) 15const accessTokenCache = new LRUCache<string, MOAuthTokenUser>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
15const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) 16const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
16 17
17// --------------------------------------------------------------------------- 18// ---------------------------------------------------------------------------
diff --git a/server/lib/peertube-socket.ts b/server/lib/peertube-socket.ts
index 17748fd18..26ced351f 100644
--- a/server/lib/peertube-socket.ts
+++ b/server/lib/peertube-socket.ts
@@ -1,8 +1,8 @@
1import * as SocketIO from 'socket.io' 1import * as SocketIO from 'socket.io'
2import { authenticateSocket } from '../middlewares' 2import { authenticateSocket } from '../middlewares'
3import { UserNotificationModel } from '../models/account/user-notification'
4import { logger } from '../helpers/logger' 3import { logger } from '../helpers/logger'
5import { Server } from 'http' 4import { Server } from 'http'
5import { UserNotificationModelForApi } from '@server/typings/models/user'
6 6
7class PeerTubeSocket { 7class PeerTubeSocket {
8 8
@@ -34,13 +34,14 @@ class PeerTubeSocket {
34 }) 34 })
35 } 35 }
36 36
37 sendNotification (userId: number, notification: UserNotificationModel) { 37 sendNotification (userId: number, notification: UserNotificationModelForApi) {
38 const sockets = this.userNotificationSockets[userId] 38 const sockets = this.userNotificationSockets[userId]
39 39
40 if (!sockets) return 40 if (!sockets) return
41 41
42 const notificationMessage = notification.toFormattedJSON()
42 for (const socket of sockets) { 43 for (const socket of sockets) {
43 socket.emit('new-notification', notification.toFormattedJSON()) 44 socket.emit('new-notification', notificationMessage)
44 } 45 }
45 } 46 }
46 47
diff --git a/server/lib/redundancy.ts b/server/lib/redundancy.ts
index 04d3ded8f..1b4ecd7c0 100644
--- a/server/lib/redundancy.ts
+++ b/server/lib/redundancy.ts
@@ -2,8 +2,9 @@ import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
2import { sendUndoCacheFile } from './activitypub/send' 2import { sendUndoCacheFile } from './activitypub/send'
3import { Transaction } from 'sequelize' 3import { Transaction } from 'sequelize'
4import { getServerActor } from '../helpers/utils' 4import { getServerActor } from '../helpers/utils'
5import { MVideoRedundancyVideo } from '@server/typings/models'
5 6
6async function removeVideoRedundancy (videoRedundancy: VideoRedundancyModel, t?: Transaction) { 7async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) {
7 const serverActor = await getServerActor() 8 const serverActor = await getServerActor()
8 9
9 // Local cache, send undo to remote instances 10 // Local cache, send undo to remote instances
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts
index cd70fd851..d326148d0 100644
--- a/server/lib/schedulers/videos-redundancy-scheduler.ts
+++ b/server/lib/schedulers/videos-redundancy-scheduler.ts
@@ -3,7 +3,6 @@ import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER }
3import { logger } from '../../helpers/logger' 3import { logger } from '../../helpers/logger'
4import { VideosRedundancy } from '../../../shared/models/redundancy' 4import { VideosRedundancy } from '../../../shared/models/redundancy'
5import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' 5import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
6import { VideoFileModel } from '../../models/video/video-file'
7import { downloadWebTorrentVideo } from '../../helpers/webtorrent' 6import { downloadWebTorrentVideo } from '../../helpers/webtorrent'
8import { join } from 'path' 7import { join } from 'path'
9import { move } from 'fs-extra' 8import { move } from 'fs-extra'
@@ -12,16 +11,31 @@ import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send'
12import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url' 11import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url'
13import { removeVideoRedundancy } from '../redundancy' 12import { removeVideoRedundancy } from '../redundancy'
14import { getOrCreateVideoAndAccountAndChannel } from '../activitypub' 13import { getOrCreateVideoAndAccountAndChannel } from '../activitypub'
15import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
16import { VideoModel } from '../../models/video/video'
17import { downloadPlaylistSegments } from '../hls' 14import { downloadPlaylistSegments } from '../hls'
18import { CONFIG } from '../../initializers/config' 15import { CONFIG } from '../../initializers/config'
16import {
17 MStreamingPlaylist,
18 MStreamingPlaylistVideo,
19 MVideoAccountLight,
20 MVideoFile,
21 MVideoFileVideo,
22 MVideoRedundancyFileVideo,
23 MVideoRedundancyStreamingPlaylistVideo,
24 MVideoRedundancyVideo,
25 MVideoWithAllFiles
26} from '@server/typings/models'
19 27
20type CandidateToDuplicate = { 28type CandidateToDuplicate = {
21 redundancy: VideosRedundancy, 29 redundancy: VideosRedundancy,
22 video: VideoModel, 30 video: MVideoWithAllFiles,
23 files: VideoFileModel[], 31 files: MVideoFile[],
24 streamingPlaylists: VideoStreamingPlaylistModel[] 32 streamingPlaylists: MStreamingPlaylist[]
33}
34
35function isMVideoRedundancyFileVideo (
36 o: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo
37): o is MVideoRedundancyFileVideo {
38 return !!(o as MVideoRedundancyFileVideo).VideoFile
25} 39}
26 40
27export class VideosRedundancyScheduler extends AbstractScheduler { 41export class VideosRedundancyScheduler extends AbstractScheduler {
@@ -102,7 +116,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
102 } 116 }
103 } 117 }
104 118
105 private async extendsRedundancy (redundancyModel: VideoRedundancyModel) { 119 private async extendsRedundancy (redundancyModel: MVideoRedundancyVideo) {
106 const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) 120 const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy)
107 // Redundancy strategy disabled, remove our redundancy instead of extending expiration 121 // Redundancy strategy disabled, remove our redundancy instead of extending expiration
108 if (!redundancy) { 122 if (!redundancy) {
@@ -172,7 +186,8 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
172 } 186 }
173 } 187 }
174 188
175 private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: VideoModel, file: VideoFileModel) { 189 private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: MVideoAccountLight, fileArg: MVideoFile) {
190 const file = fileArg as MVideoFileVideo
176 file.Video = video 191 file.Video = video
177 192
178 const serverActor = await getServerActor() 193 const serverActor = await getServerActor()
@@ -187,7 +202,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
187 const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file)) 202 const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file))
188 await move(tmpPath, destPath, { overwrite: true }) 203 await move(tmpPath, destPath, { overwrite: true })
189 204
190 const createdModel = await VideoRedundancyModel.create({ 205 const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({
191 expiresOn: this.buildNewExpiration(redundancy.minLifetime), 206 expiresOn: this.buildNewExpiration(redundancy.minLifetime),
192 url: getVideoCacheFileActivityPubUrl(file), 207 url: getVideoCacheFileActivityPubUrl(file),
193 fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), 208 fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL),
@@ -203,7 +218,12 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
203 logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url) 218 logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url)
204 } 219 }
205 220
206 private async createStreamingPlaylistRedundancy (redundancy: VideosRedundancy, video: VideoModel, playlist: VideoStreamingPlaylistModel) { 221 private async createStreamingPlaylistRedundancy (
222 redundancy: VideosRedundancy,
223 video: MVideoAccountLight,
224 playlistArg: MStreamingPlaylist
225 ) {
226 const playlist = playlistArg as MStreamingPlaylistVideo
207 playlist.Video = video 227 playlist.Video = video
208 228
209 const serverActor = await getServerActor() 229 const serverActor = await getServerActor()
@@ -213,7 +233,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
213 const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) 233 const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid)
214 await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) 234 await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT)
215 235
216 const createdModel = await VideoRedundancyModel.create({ 236 const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({
217 expiresOn: this.buildNewExpiration(redundancy.minLifetime), 237 expiresOn: this.buildNewExpiration(redundancy.minLifetime),
218 url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), 238 url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist),
219 fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), 239 fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL),
@@ -229,7 +249,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
229 logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url) 249 logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url)
230 } 250 }
231 251
232 private async extendsExpirationOf (redundancy: VideoRedundancyModel, expiresAfterMs: number) { 252 private async extendsExpirationOf (redundancy: MVideoRedundancyVideo, expiresAfterMs: number) {
233 logger.info('Extending expiration of %s.', redundancy.url) 253 logger.info('Extending expiration of %s.', redundancy.url)
234 254
235 const serverActor = await getServerActor() 255 const serverActor = await getServerActor()
@@ -243,7 +263,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
243 private async purgeCacheIfNeeded (candidateToDuplicate: CandidateToDuplicate) { 263 private async purgeCacheIfNeeded (candidateToDuplicate: CandidateToDuplicate) {
244 while (this.isTooHeavy(candidateToDuplicate)) { 264 while (this.isTooHeavy(candidateToDuplicate)) {
245 const redundancy = candidateToDuplicate.redundancy 265 const redundancy = candidateToDuplicate.redundancy
246 const toDelete = await VideoRedundancyModel.loadOldestLocalThatAlreadyExpired(redundancy.strategy, redundancy.minLifetime) 266 const toDelete = await VideoRedundancyModel.loadOldestLocalExpired(redundancy.strategy, redundancy.minLifetime)
247 if (!toDelete) return 267 if (!toDelete) return
248 268
249 await removeVideoRedundancy(toDelete) 269 await removeVideoRedundancy(toDelete)
@@ -263,19 +283,18 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
263 return new Date(Date.now() + expiresAfterMs) 283 return new Date(Date.now() + expiresAfterMs)
264 } 284 }
265 285
266 private buildEntryLogId (object: VideoRedundancyModel) { 286 private buildEntryLogId (object: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo) {
267 if (object.VideoFile) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}` 287 if (isMVideoRedundancyFileVideo(object)) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}`
268 288
269 return `${object.VideoStreamingPlaylist.playlistUrl}` 289 return `${object.VideoStreamingPlaylist.playlistUrl}`
270 } 290 }
271 291
272 private getTotalFileSizes (files: VideoFileModel[], playlists: VideoStreamingPlaylistModel[]) { 292 private getTotalFileSizes (files: MVideoFile[], playlists: MStreamingPlaylist[]) {
273 const fileReducer = (previous: number, current: VideoFileModel) => previous + current.size 293 const fileReducer = (previous: number, current: MVideoFile) => previous + current.size
274 294
275 const totalSize = files.reduce(fileReducer, 0) 295 const totalSize = files.reduce(fileReducer, 0)
276 if (playlists.length === 0) return totalSize
277 296
278 return totalSize * playlists.length 297 return totalSize + (totalSize * playlists.length)
279 } 298 }
280 299
281 private async loadAndRefreshVideo (videoUrl: string) { 300 private async loadAndRefreshVideo (videoUrl: string) {
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts
index a59773f5a..84791955e 100644
--- a/server/lib/thumbnail.ts
+++ b/server/lib/thumbnail.ts
@@ -1,20 +1,20 @@
1import { VideoFileModel } from '../models/video/video-file'
2import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' 1import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils'
3import { CONFIG } from '../initializers/config' 2import { CONFIG } from '../initializers/config'
4import { PREVIEWS_SIZE, THUMBNAILS_SIZE, ASSETS_PATH } from '../initializers/constants' 3import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants'
5import { VideoModel } from '../models/video/video'
6import { ThumbnailModel } from '../models/video/thumbnail' 4import { ThumbnailModel } from '../models/video/thumbnail'
7import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' 5import { ThumbnailType } from '../../shared/models/videos/thumbnail.type'
8import { processImage } from '../helpers/image-utils' 6import { processImage } from '../helpers/image-utils'
9import { join } from 'path' 7import { join } from 'path'
10import { downloadImage } from '../helpers/requests' 8import { downloadImage } from '../helpers/requests'
11import { VideoPlaylistModel } from '../models/video/video-playlist' 9import { MVideoPlaylistThumbnail } from '../typings/models/video/video-playlist'
10import { MVideoFile, MVideoThumbnail } from '../typings/models'
11import { MThumbnail } from '../typings/models/video/thumbnail'
12 12
13type ImageSize = { height: number, width: number } 13type ImageSize = { height: number, width: number }
14 14
15function createPlaylistMiniatureFromExisting ( 15function createPlaylistMiniatureFromExisting (
16 inputPath: string, 16 inputPath: string,
17 playlist: VideoPlaylistModel, 17 playlist: MVideoPlaylistThumbnail,
18 automaticallyGenerated: boolean, 18 automaticallyGenerated: boolean,
19 keepOriginal = false, 19 keepOriginal = false,
20 size?: ImageSize 20 size?: ImageSize
@@ -26,7 +26,7 @@ function createPlaylistMiniatureFromExisting (
26 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) 26 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail })
27} 27}
28 28
29function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylistModel, size?: ImageSize) { 29function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: MVideoPlaylistThumbnail, size?: ImageSize) {
30 const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) 30 const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
31 const type = ThumbnailType.MINIATURE 31 const type = ThumbnailType.MINIATURE
32 32
@@ -34,7 +34,7 @@ function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylis
34 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) 34 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl })
35} 35}
36 36
37function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) { 37function createVideoMiniatureFromUrl (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) {
38 const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) 38 const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
39 const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height }) 39 const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height })
40 40
@@ -43,7 +43,7 @@ function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type:
43 43
44function createVideoMiniatureFromExisting ( 44function createVideoMiniatureFromExisting (
45 inputPath: string, 45 inputPath: string,
46 video: VideoModel, 46 video: MVideoThumbnail,
47 type: ThumbnailType, 47 type: ThumbnailType,
48 automaticallyGenerated: boolean, 48 automaticallyGenerated: boolean,
49 size?: ImageSize 49 size?: ImageSize
@@ -54,7 +54,7 @@ function createVideoMiniatureFromExisting (
54 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) 54 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail })
55} 55}
56 56
57function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) { 57function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile, type: ThumbnailType) {
58 const input = video.getVideoFilePath(videoFile) 58 const input = video.getVideoFilePath(videoFile)
59 59
60 const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type) 60 const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type)
@@ -65,7 +65,7 @@ function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, t
65 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail }) 65 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail })
66} 66}
67 67
68function createPlaceholderThumbnail (fileUrl: string, video: VideoModel, type: ThumbnailType, size: ImageSize) { 68function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) {
69 const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) 69 const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
70 70
71 const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() 71 const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel()
@@ -90,7 +90,7 @@ export {
90 createPlaylistMiniatureFromExisting 90 createPlaylistMiniatureFromExisting
91} 91}
92 92
93function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) { 93function buildMetadataFromPlaylist (playlist: MVideoPlaylistThumbnail, size: ImageSize) {
94 const filename = playlist.generateThumbnailName() 94 const filename = playlist.generateThumbnailName()
95 const basePath = CONFIG.STORAGE.THUMBNAILS_DIR 95 const basePath = CONFIG.STORAGE.THUMBNAILS_DIR
96 96
@@ -104,7 +104,7 @@ function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSiz
104 } 104 }
105} 105}
106 106
107function buildMetadataFromVideo (video: VideoModel, type: ThumbnailType, size?: ImageSize) { 107function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) {
108 const existingThumbnail = Array.isArray(video.Thumbnails) 108 const existingThumbnail = Array.isArray(video.Thumbnails)
109 ? video.Thumbnails.find(t => t.type === type) 109 ? video.Thumbnails.find(t => t.type === type)
110 : undefined 110 : undefined
@@ -148,7 +148,7 @@ async function createThumbnailFromFunction (parameters: {
148 type: ThumbnailType, 148 type: ThumbnailType,
149 automaticallyGenerated?: boolean, 149 automaticallyGenerated?: boolean,
150 fileUrl?: string, 150 fileUrl?: string,
151 existingThumbnail?: ThumbnailModel 151 existingThumbnail?: MThumbnail
152}) { 152}) {
153 const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters 153 const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters
154 154
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 0e4007770..d84aff464 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -2,10 +2,8 @@ import * as uuidv4 from 'uuid/v4'
2import { ActivityPubActorType } from '../../shared/models/activitypub' 2import { ActivityPubActorType } from '../../shared/models/activitypub'
3import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' 3import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants'
4import { AccountModel } from '../models/account/account' 4import { AccountModel } from '../models/account/account'
5import { UserModel } from '../models/account/user'
6import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub' 5import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub'
7import { createVideoChannel } from './video-channel' 6import { createLocalVideoChannel } from './video-channel'
8import { VideoChannelModel } from '../models/video/video-channel'
9import { ActorModel } from '../models/activitypub/actor' 7import { ActorModel } from '../models/activitypub/actor'
10import { UserNotificationSettingModel } from '../models/account/user-notification-setting' 8import { UserNotificationSettingModel } from '../models/account/user-notification-setting'
11import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' 9import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users'
@@ -14,14 +12,17 @@ import { sequelizeTypescript } from '../initializers/database'
14import { Transaction } from 'sequelize/types' 12import { Transaction } from 'sequelize/types'
15import { Redis } from './redis' 13import { Redis } from './redis'
16import { Emailer } from './emailer' 14import { Emailer } from './emailer'
15import { MAccountDefault, MActorDefault, MChannelActor } from '../typings/models'
16import { MUser, MUserDefault, MUserId } from '../typings/models/user'
17 17
18type ChannelNames = { name: string, displayName: string } 18type ChannelNames = { name: string, displayName: string }
19
19async function createUserAccountAndChannelAndPlaylist (parameters: { 20async function createUserAccountAndChannelAndPlaylist (parameters: {
20 userToCreate: UserModel, 21 userToCreate: MUser,
21 userDisplayName?: string, 22 userDisplayName?: string,
22 channelNames?: ChannelNames, 23 channelNames?: ChannelNames,
23 validateUser?: boolean 24 validateUser?: boolean
24}) { 25}): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> {
25 const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters 26 const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters
26 27
27 const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { 28 const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
@@ -30,7 +31,7 @@ async function createUserAccountAndChannelAndPlaylist (parameters: {
30 validate: validateUser 31 validate: validateUser
31 } 32 }
32 33
33 const userCreated = await userToCreate.save(userOptions) 34 const userCreated: MUserDefault = await userToCreate.save(userOptions)
34 userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t) 35 userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t)
35 36
36 const accountCreated = await createLocalAccountWithoutKeys({ 37 const accountCreated = await createLocalAccountWithoutKeys({
@@ -43,22 +44,22 @@ async function createUserAccountAndChannelAndPlaylist (parameters: {
43 userCreated.Account = accountCreated 44 userCreated.Account = accountCreated
44 45
45 const channelAttributes = await buildChannelAttributes(userCreated, channelNames) 46 const channelAttributes = await buildChannelAttributes(userCreated, channelNames)
46 const videoChannel = await createVideoChannel(channelAttributes, accountCreated, t) 47 const videoChannel = await createLocalVideoChannel(channelAttributes, accountCreated, t)
47 48
48 const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) 49 const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t)
49 50
50 return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist } 51 return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist }
51 }) 52 })
52 53
53 const [ accountKeys, channelKeys ] = await Promise.all([ 54 const [ accountActorWithKeys, channelActorWithKeys ] = await Promise.all([
54 setAsyncActorKeys(account.Actor), 55 setAsyncActorKeys(account.Actor),
55 setAsyncActorKeys(videoChannel.Actor) 56 setAsyncActorKeys(videoChannel.Actor)
56 ]) 57 ])
57 58
58 account.Actor = accountKeys 59 account.Actor = accountActorWithKeys
59 videoChannel.Actor = channelKeys 60 videoChannel.Actor = channelActorWithKeys
60 61
61 return { user, account, videoChannel } as { user: UserModel, account: AccountModel, videoChannel: VideoChannelModel } 62 return { user, account, videoChannel }
62} 63}
63 64
64async function createLocalAccountWithoutKeys (parameters: { 65async function createLocalAccountWithoutKeys (parameters: {
@@ -73,7 +74,7 @@ async function createLocalAccountWithoutKeys (parameters: {
73 const url = getAccountActivityPubUrl(name) 74 const url = getAccountActivityPubUrl(name)
74 75
75 const actorInstance = buildActorInstance(type, url, name) 76 const actorInstance = buildActorInstance(type, url, name)
76 const actorInstanceCreated = await actorInstance.save({ transaction: t }) 77 const actorInstanceCreated: MActorDefault = await actorInstance.save({ transaction: t })
77 78
78 const accountInstance = new AccountModel({ 79 const accountInstance = new AccountModel({
79 name: displayName || name, 80 name: displayName || name,
@@ -82,7 +83,7 @@ async function createLocalAccountWithoutKeys (parameters: {
82 actorId: actorInstanceCreated.id 83 actorId: actorInstanceCreated.id
83 }) 84 })
84 85
85 const accountInstanceCreated = await accountInstance.save({ transaction: t }) 86 const accountInstanceCreated: MAccountDefault = await accountInstance.save({ transaction: t })
86 accountInstanceCreated.Actor = actorInstanceCreated 87 accountInstanceCreated.Actor = actorInstanceCreated
87 88
88 return accountInstanceCreated 89 return accountInstanceCreated
@@ -102,7 +103,7 @@ async function createApplicationActor (applicationId: number) {
102 return accountCreated 103 return accountCreated
103} 104}
104 105
105async function sendVerifyUserEmail (user: UserModel, isPendingEmail = false) { 106async function sendVerifyUserEmail (user: MUser, isPendingEmail = false) {
106 const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id) 107 const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id)
107 let url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString 108 let url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString
108 109
@@ -124,7 +125,7 @@ export {
124 125
125// --------------------------------------------------------------------------- 126// ---------------------------------------------------------------------------
126 127
127function createDefaultUserNotificationSettings (user: UserModel, t: Transaction | undefined) { 128function createDefaultUserNotificationSettings (user: MUserId, t: Transaction | undefined) {
128 const values: UserNotificationSetting & { userId: number } = { 129 const values: UserNotificationSetting & { userId: number } = {
129 userId: user.id, 130 userId: user.id,
130 newVideoFromSubscription: UserNotificationSettingValue.WEB, 131 newVideoFromSubscription: UserNotificationSettingValue.WEB,
@@ -143,7 +144,7 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Transaction
143 return UserNotificationSettingModel.create(values, { transaction: t }) 144 return UserNotificationSettingModel.create(values, { transaction: t })
144} 145}
145 146
146async function buildChannelAttributes (user: UserModel, channelNames?: ChannelNames) { 147async function buildChannelAttributes (user: MUser, channelNames?: ChannelNames) {
147 if (channelNames) return channelNames 148 if (channelNames) return channelNames
148 149
149 let channelName = user.username + '_channel' 150 let channelName = user.username + '_channel'
diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts
index bdaecd8e2..a0fc26e84 100644
--- a/server/lib/video-blacklist.ts
+++ b/server/lib/video-blacklist.ts
@@ -2,16 +2,15 @@ import { Transaction } from 'sequelize'
2import { CONFIG } from '../initializers/config' 2import { CONFIG } from '../initializers/config'
3import { UserRight, VideoBlacklistType } from '../../shared/models' 3import { UserRight, VideoBlacklistType } from '../../shared/models'
4import { VideoBlacklistModel } from '../models/video/video-blacklist' 4import { VideoBlacklistModel } from '../models/video/video-blacklist'
5import { UserModel } from '../models/account/user'
6import { VideoModel } from '../models/video/video'
7import { logger } from '../helpers/logger' 5import { logger } from '../helpers/logger'
8import { UserAdminFlag } from '../../shared/models/users/user-flag.model' 6import { UserAdminFlag } from '../../shared/models/users/user-flag.model'
9import { Hooks } from './plugins/hooks' 7import { Hooks } from './plugins/hooks'
10import { Notifier } from './notifier' 8import { Notifier } from './notifier'
9import { MUser, MVideoBlacklist, MVideoWithBlacklistLight } from '@server/typings/models'
11 10
12async function autoBlacklistVideoIfNeeded (parameters: { 11async function autoBlacklistVideoIfNeeded (parameters: {
13 video: VideoModel, 12 video: MVideoWithBlacklistLight,
14 user?: UserModel, 13 user?: MUser,
15 isRemote: boolean, 14 isRemote: boolean,
16 isNew: boolean, 15 isNew: boolean,
17 notify?: boolean, 16 notify?: boolean,
@@ -32,7 +31,7 @@ async function autoBlacklistVideoIfNeeded (parameters: {
32 reason: 'Auto-blacklisted. Moderator review required.', 31 reason: 'Auto-blacklisted. Moderator review required.',
33 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED 32 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
34 } 33 }
35 const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate({ 34 const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate<MVideoBlacklist>({
36 where: { 35 where: {
37 videoId: video.id 36 videoId: video.id
38 }, 37 },
@@ -49,10 +48,10 @@ async function autoBlacklistVideoIfNeeded (parameters: {
49} 48}
50 49
51async function autoBlacklistNeeded (parameters: { 50async function autoBlacklistNeeded (parameters: {
52 video: VideoModel, 51 video: MVideoWithBlacklistLight,
53 isRemote: boolean, 52 isRemote: boolean,
54 isNew: boolean, 53 isNew: boolean,
55 user?: UserModel 54 user?: MUser
56}) { 55}) {
57 const { user, video, isRemote, isNew } = parameters 56 const { user, video, isRemote, isNew } = parameters
58 57
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts
index ee0482c36..41eab456b 100644
--- a/server/lib/video-channel.ts
+++ b/server/lib/video-channel.ts
@@ -1,12 +1,19 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import * as uuidv4 from 'uuid/v4' 2import * as uuidv4 from 'uuid/v4'
3import { VideoChannelCreate } from '../../shared/models' 3import { VideoChannelCreate } from '../../shared/models'
4import { AccountModel } from '../models/account/account'
5import { VideoChannelModel } from '../models/video/video-channel' 4import { VideoChannelModel } from '../models/video/video-channel'
6import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub' 5import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub'
7import { VideoModel } from '../models/video/video' 6import { VideoModel } from '../models/video/video'
7import { MAccountId, MChannelDefault, MChannelId } from '../typings/models'
8 8
9async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) { 9type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault &
10 { Account?: T }
11
12async function createLocalVideoChannel <T extends MAccountId> (
13 videoChannelInfo: VideoChannelCreate,
14 account: T,
15 t: Sequelize.Transaction
16): Promise<CustomVideoChannelModelAccount<T>> {
10 const uuid = uuidv4() 17 const uuid = uuidv4()
11 const url = getVideoChannelActivityPubUrl(videoChannelInfo.name) 18 const url = getVideoChannelActivityPubUrl(videoChannelInfo.name)
12 const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid) 19 const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid)
@@ -21,10 +28,10 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
21 actorId: actorInstanceCreated.id 28 actorId: actorInstanceCreated.id
22 } 29 }
23 30
24 const videoChannel = VideoChannelModel.build(videoChannelData) 31 const videoChannel = new VideoChannelModel(videoChannelData)
25 32
26 const options = { transaction: t } 33 const options = { transaction: t }
27 const videoChannelCreated = await videoChannel.save(options) 34 const videoChannelCreated: CustomVideoChannelModelAccount<T> = await videoChannel.save(options) as MChannelDefault
28 35
29 // Do not forget to add Account/Actor information to the created video channel 36 // Do not forget to add Account/Actor information to the created video channel
30 videoChannelCreated.Account = account 37 videoChannelCreated.Account = account
@@ -34,7 +41,7 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
34 return videoChannelCreated 41 return videoChannelCreated
35} 42}
36 43
37async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) { 44async function federateAllVideosOfChannel (videoChannel: MChannelId) {
38 const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel) 45 const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel)
39 46
40 for (const videoId of videoIds) { 47 for (const videoId of videoIds) {
@@ -47,6 +54,6 @@ async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) {
47// --------------------------------------------------------------------------- 54// ---------------------------------------------------------------------------
48 55
49export { 56export {
50 createVideoChannel, 57 createLocalVideoChannel,
51 federateAllVideosOfChannel 58 federateAllVideosOfChannel
52} 59}
diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts
index 449aa74cb..bb811bd2c 100644
--- a/server/lib/video-comment.ts
+++ b/server/lib/video-comment.ts
@@ -1,17 +1,16 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { ResultList } from '../../shared/models' 2import { ResultList } from '../../shared/models'
3import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' 3import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model'
4import { AccountModel } from '../models/account/account'
5import { VideoModel } from '../models/video/video'
6import { VideoCommentModel } from '../models/video/video-comment' 4import { VideoCommentModel } from '../models/video/video-comment'
7import { getVideoCommentActivityPubUrl } from './activitypub' 5import { getVideoCommentActivityPubUrl } from './activitypub'
8import { sendCreateVideoComment } from './activitypub/send' 6import { sendCreateVideoComment } from './activitypub/send'
7import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models'
9 8
10async function createVideoComment (obj: { 9async function createVideoComment (obj: {
11 text: string, 10 text: string,
12 inReplyToComment: VideoCommentModel | null, 11 inReplyToComment: MComment | null,
13 video: VideoModel 12 video: MVideoFullLight,
14 account: AccountModel 13 account: MAccountDefault
15}, t: Sequelize.Transaction) { 14}, t: Sequelize.Transaction) {
16 let originCommentId: number | null = null 15 let originCommentId: number | null = null
17 let inReplyToCommentId: number | null = null 16 let inReplyToCommentId: number | null = null
@@ -32,7 +31,7 @@ async function createVideoComment (obj: {
32 31
33 comment.url = getVideoCommentActivityPubUrl(obj.video, comment) 32 comment.url = getVideoCommentActivityPubUrl(obj.video, comment)
34 33
35 const savedComment = await comment.save({ transaction: t }) 34 const savedComment: MCommentOwnerVideoReply = await comment.save({ transaction: t })
36 savedComment.InReplyToVideoComment = obj.inReplyToComment 35 savedComment.InReplyToVideoComment = obj.inReplyToComment
37 savedComment.Video = obj.video 36 savedComment.Video = obj.video
38 savedComment.Account = obj.account 37 savedComment.Account = obj.account
diff --git a/server/lib/video-playlist.ts b/server/lib/video-playlist.ts
index 6e214e60f..29b70cfda 100644
--- a/server/lib/video-playlist.ts
+++ b/server/lib/video-playlist.ts
@@ -1,12 +1,13 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { AccountModel } from '../models/account/account'
3import { VideoPlaylistModel } from '../models/video/video-playlist' 2import { VideoPlaylistModel } from '../models/video/video-playlist'
4import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model' 3import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model'
5import { getVideoPlaylistActivityPubUrl } from './activitypub' 4import { getVideoPlaylistActivityPubUrl } from './activitypub'
6import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model' 5import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model'
6import { MAccount } from '../typings/models'
7import { MVideoPlaylistOwner } from '../typings/models/video/video-playlist'
7 8
8async function createWatchLaterPlaylist (account: AccountModel, t: Sequelize.Transaction) { 9async function createWatchLaterPlaylist (account: MAccount, t: Sequelize.Transaction) {
9 const videoPlaylist = new VideoPlaylistModel({ 10 const videoPlaylist: MVideoPlaylistOwner = new VideoPlaylistModel({
10 name: 'Watch later', 11 name: 'Watch later',
11 privacy: VideoPlaylistPrivacy.PRIVATE, 12 privacy: VideoPlaylistPrivacy.PRIVATE,
12 type: VideoPlaylistType.WATCH_LATER, 13 type: VideoPlaylistType.WATCH_LATER,
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts
index ba6b29163..a204c0c63 100644
--- a/server/lib/video-transcoding.ts
+++ b/server/lib/video-transcoding.ts
@@ -5,16 +5,16 @@ import { ensureDir, move, remove, stat } from 'fs-extra'
5import { logger } from '../helpers/logger' 5import { logger } from '../helpers/logger'
6import { VideoResolution } from '../../shared/models/videos' 6import { VideoResolution } from '../../shared/models/videos'
7import { VideoFileModel } from '../models/video/video-file' 7import { VideoFileModel } from '../models/video/video-file'
8import { VideoModel } from '../models/video/video'
9import { updateMasterHLSPlaylist, updateSha256Segments } from './hls' 8import { updateMasterHLSPlaylist, updateSha256Segments } from './hls'
10import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' 9import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
11import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' 10import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
12import { CONFIG } from '../initializers/config' 11import { CONFIG } from '../initializers/config'
12import { MVideoFile, MVideoWithFile, MVideoWithFileThumbnail } from '@server/typings/models'
13 13
14/** 14/**
15 * Optimize the original video file and replace it. The resolution is not changed. 15 * Optimize the original video file and replace it. The resolution is not changed.
16 */ 16 */
17async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { 17async function optimizeVideofile (video: MVideoWithFile, inputVideoFileArg?: MVideoFile) {
18 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 18 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
19 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR 19 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
20 const newExtname = '.mp4' 20 const newExtname = '.mp4'
@@ -57,7 +57,7 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
57/** 57/**
58 * Transcode the original video file to a lower resolution. 58 * Transcode the original video file to a lower resolution.
59 */ 59 */
60async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { 60async function transcodeOriginalVideofile (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean) {
61 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 61 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
62 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR 62 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
63 const extname = '.mp4' 63 const extname = '.mp4'
@@ -87,7 +87,7 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR
87 return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) 87 return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
88} 88}
89 89
90async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) { 90async function mergeAudioVideofile (video: MVideoWithFileThumbnail, resolution: VideoResolution) {
91 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 91 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
92 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR 92 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
93 const newExtname = '.mp4' 93 const newExtname = '.mp4'
@@ -117,7 +117,7 @@ async function mergeAudioVideofile (video: VideoModel, resolution: VideoResoluti
117 return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) 117 return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
118} 118}
119 119
120async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { 120async function generateHlsPlaylist (video: MVideoWithFile, resolution: VideoResolution, isPortraitMode: boolean) {
121 const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) 121 const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
122 await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)) 122 await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid))
123 123
@@ -165,14 +165,14 @@ export {
165 165
166// --------------------------------------------------------------------------- 166// ---------------------------------------------------------------------------
167 167
168async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) { 168async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoFile, transcodingPath: string, outputPath: string) {
169 const stats = await stat(transcodingPath) 169 const stats = await stat(transcodingPath)
170 const fps = await getVideoFileFPS(transcodingPath) 170 const fps = await getVideoFileFPS(transcodingPath)
171 171
172 await move(transcodingPath, outputPath) 172 await move(transcodingPath, outputPath)
173 173
174 videoFile.set('size', stats.size) 174 videoFile.size = stats.size
175 videoFile.set('fps', fps) 175 videoFile.fps = fps
176 176
177 await video.createTorrentAndSetInfoHash(videoFile) 177 await video.createTorrentAndSetInfoHash(videoFile)
178 178
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts
index b1e5b5236..bea213d27 100644
--- a/server/middlewares/activitypub.ts
+++ b/server/middlewares/activitypub.ts
@@ -101,6 +101,8 @@ async function checkJsonLDSignature (req: Request, res: Response) {
101 const verified = await isJsonLDSignatureVerified(actor, req.body) 101 const verified = await isJsonLDSignatureVerified(actor, req.body)
102 102
103 if (verified !== true) { 103 if (verified !== true) {
104 logger.warn('Signature not verified.', req.body)
105
104 res.sendStatus(403) 106 res.sendStatus(403)
105 return false 107 return false
106 } 108 }
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts
index c3d772297..788735663 100644
--- a/server/middlewares/validators/follows.ts
+++ b/server/middlewares/validators/follows.ts
@@ -10,6 +10,7 @@ import { areValidationErrors } from './utils'
10import { ActorModel } from '../../models/activitypub/actor' 10import { ActorModel } from '../../models/activitypub/actor'
11import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' 11import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
12import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' 12import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor'
13import { MActorFollowActorsDefault } from '@server/typings/models'
13 14
14const followValidator = [ 15const followValidator = [
15 body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'), 16 body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'),
@@ -65,7 +66,7 @@ const getFollowerValidator = [
65 66
66 if (areValidationErrors(req, res)) return 67 if (areValidationErrors(req, res)) return
67 68
68 let follow: ActorFollowModel 69 let follow: MActorFollowActorsDefault
69 try { 70 try {
70 const actorUrl = await loadActorUrlOrGetFromWebfinger(req.params.nameWithHost) 71 const actorUrl = await loadActorUrlOrGetFromWebfinger(req.params.nameWithHost)
71 const actor = await ActorModel.loadByUrl(actorUrl) 72 const actor = await ActorModel.loadByUrl(actorUrl)
diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts
index 1fdac0e4e..e65d3b8d3 100644
--- a/server/middlewares/validators/redundancy.ts
+++ b/server/middlewares/validators/redundancy.ts
@@ -24,7 +24,7 @@ const videoFileRedundancyGetValidator = [
24 if (areValidationErrors(req, res)) return 24 if (areValidationErrors(req, res)) return
25 if (!await doesVideoExist(req.params.videoId, res)) return 25 if (!await doesVideoExist(req.params.videoId, res)) return
26 26
27 const video = res.locals.video 27 const video = res.locals.videoAll
28 const videoFile = video.VideoFiles.find(f => { 28 const videoFile = video.VideoFiles.find(f => {
29 return f.resolution === req.params.resolution && (!req.params.fps || f.fps === req.params.fps) 29 return f.resolution === req.params.resolution && (!req.params.fps || f.fps === req.params.fps)
30 }) 30 })
@@ -50,7 +50,7 @@ const videoPlaylistRedundancyGetValidator = [
50 if (areValidationErrors(req, res)) return 50 if (areValidationErrors(req, res)) return
51 if (!await doesVideoExist(req.params.videoId, res)) return 51 if (!await doesVideoExist(req.params.videoId, res)) return
52 52
53 const video = res.locals.video 53 const video = res.locals.videoAll
54 const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p === req.params.streamingPlaylistType) 54 const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p === req.params.streamingPlaylistType)
55 55
56 if (!videoStreamingPlaylist) return res.status(404).json({ error: 'Video playlist not found.' }) 56 if (!videoStreamingPlaylist) return res.status(404).json({ error: 'Video playlist not found.' })
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index 8ee2ec1f5..26f43cec7 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -31,6 +31,7 @@ import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
31import { isThemeRegistered } from '../../lib/plugins/theme-utils' 31import { isThemeRegistered } from '../../lib/plugins/theme-utils'
32import { doesVideoExist } from '../../helpers/middlewares' 32import { doesVideoExist } from '../../helpers/middlewares'
33import { UserRole } from '../../../shared/models/users' 33import { UserRole } from '../../../shared/models/users'
34import { MUserDefault } from '@server/typings/models'
34 35
35const usersAddValidator = [ 36const usersAddValidator = [
36 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), 37 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
@@ -462,7 +463,7 @@ async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email:
462 return true 463 return true
463} 464}
464 465
465async function checkUserExist (finder: () => Bluebird<UserModel>, res: express.Response, abortResponse = true) { 466async function checkUserExist (finder: () => Bluebird<MUserDefault>, res: express.Response, abortResponse = true) {
466 const user = await finder() 467 const user = await finder()
467 468
468 if (!user) { 469 if (!user) {
diff --git a/server/middlewares/validators/videos/video-abuses.ts b/server/middlewares/validators/videos/video-abuses.ts
index e27d91bb1..a4aef4024 100644
--- a/server/middlewares/validators/videos/video-abuses.ts
+++ b/server/middlewares/validators/videos/video-abuses.ts
@@ -33,7 +33,7 @@ const videoAbuseGetValidator = [
33 33
34 if (areValidationErrors(req, res)) return 34 if (areValidationErrors(req, res)) return
35 if (!await doesVideoExist(req.params.videoId, res)) return 35 if (!await doesVideoExist(req.params.videoId, res)) return
36 if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return 36 if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return
37 37
38 return next() 38 return next()
39 } 39 }
@@ -54,7 +54,7 @@ const videoAbuseUpdateValidator = [
54 54
55 if (areValidationErrors(req, res)) return 55 if (areValidationErrors(req, res)) return
56 if (!await doesVideoExist(req.params.videoId, res)) return 56 if (!await doesVideoExist(req.params.videoId, res)) return
57 if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return 57 if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return
58 58
59 return next() 59 return next()
60 } 60 }
diff --git a/server/middlewares/validators/videos/video-blacklist.ts b/server/middlewares/validators/videos/video-blacklist.ts
index 3e8c5b30c..5440e57e7 100644
--- a/server/middlewares/validators/videos/video-blacklist.ts
+++ b/server/middlewares/validators/videos/video-blacklist.ts
@@ -14,7 +14,7 @@ const videosBlacklistRemoveValidator = [
14 14
15 if (areValidationErrors(req, res)) return 15 if (areValidationErrors(req, res)) return
16 if (!await doesVideoExist(req.params.videoId, res)) return 16 if (!await doesVideoExist(req.params.videoId, res)) return
17 if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return 17 if (!await doesVideoBlacklistExist(res.locals.videoAll.id, res)) return
18 18
19 return next() 19 return next()
20 } 20 }
@@ -36,7 +36,7 @@ const videosBlacklistAddValidator = [
36 if (areValidationErrors(req, res)) return 36 if (areValidationErrors(req, res)) return
37 if (!await doesVideoExist(req.params.videoId, res)) return 37 if (!await doesVideoExist(req.params.videoId, res)) return
38 38
39 const video = res.locals.video 39 const video = res.locals.videoAll
40 if (req.body.unfederate === true && video.remote === true) { 40 if (req.body.unfederate === true && video.remote === true) {
41 return res 41 return res
42 .status(409) 42 .status(409)
@@ -59,7 +59,7 @@ const videosBlacklistUpdateValidator = [
59 59
60 if (areValidationErrors(req, res)) return 60 if (areValidationErrors(req, res)) return
61 if (!await doesVideoExist(req.params.videoId, res)) return 61 if (!await doesVideoExist(req.params.videoId, res)) return
62 if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return 62 if (!await doesVideoBlacklistExist(res.locals.videoAll.id, res)) return
63 63
64 return next() 64 return next()
65 } 65 }
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts
index f5610222a..2fb1da5ce 100644
--- a/server/middlewares/validators/videos/video-captions.ts
+++ b/server/middlewares/validators/videos/video-captions.ts
@@ -26,7 +26,7 @@ const addVideoCaptionValidator = [
26 26
27 // Check if the user who did the request is able to update the video 27 // Check if the user who did the request is able to update the video
28 const user = res.locals.oauth.token.User 28 const user = res.locals.oauth.token.User
29 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) 29 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
30 30
31 return next() 31 return next()
32 } 32 }
@@ -41,11 +41,11 @@ const deleteVideoCaptionValidator = [
41 41
42 if (areValidationErrors(req, res)) return 42 if (areValidationErrors(req, res)) return
43 if (!await doesVideoExist(req.params.videoId, res)) return 43 if (!await doesVideoExist(req.params.videoId, res)) return
44 if (!await doesVideoCaptionExist(res.locals.video, req.params.captionLanguage, res)) return 44 if (!await doesVideoCaptionExist(res.locals.videoAll, req.params.captionLanguage, res)) return
45 45
46 // Check if the user who did the request is able to update the video 46 // Check if the user who did the request is able to update the video
47 const user = res.locals.oauth.token.User 47 const user = res.locals.oauth.token.User
48 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return 48 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return
49 49
50 return next() 50 return next()
51 } 51 }
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts
index 3ee5064fc..d21274527 100644
--- a/server/middlewares/validators/videos/video-channels.ts
+++ b/server/middlewares/validators/videos/video-channels.ts
@@ -7,13 +7,13 @@ import {
7 isVideoChannelSupportValid 7 isVideoChannelSupportValid
8} from '../../../helpers/custom-validators/video-channels' 8} from '../../../helpers/custom-validators/video-channels'
9import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
10import { UserModel } from '../../../models/account/user'
11import { VideoChannelModel } from '../../../models/video/video-channel' 10import { VideoChannelModel } from '../../../models/video/video-channel'
12import { areValidationErrors } from '../utils' 11import { areValidationErrors } from '../utils'
13import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' 12import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor'
14import { ActorModel } from '../../../models/activitypub/actor' 13import { ActorModel } from '../../../models/activitypub/actor'
15import { isBooleanValid } from '../../../helpers/custom-validators/misc' 14import { isBooleanValid } from '../../../helpers/custom-validators/misc'
16import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares' 15import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares'
16import { MChannelAccountDefault, MUser } from '@server/typings/models'
17 17
18const videoChannelsAddValidator = [ 18const videoChannelsAddValidator = [
19 body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), 19 body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
@@ -131,7 +131,7 @@ export {
131 131
132// --------------------------------------------------------------------------- 132// ---------------------------------------------------------------------------
133 133
134function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) { 134function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) {
135 if (videoChannel.Actor.isOwned() === false) { 135 if (videoChannel.Actor.isOwned() === false) {
136 res.status(403) 136 res.status(403)
137 .json({ error: 'Cannot remove video channel of another server.' }) 137 .json({ error: 'Cannot remove video channel of another server.' })
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
index 83a0c24b0..8adbb02ba 100644
--- a/server/middlewares/validators/videos/video-comments.ts
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -4,13 +4,13 @@ import { UserRight } from '../../../../shared'
4import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' 4import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
5import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' 5import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments'
6import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
7import { UserModel } from '../../../models/account/user'
8import { VideoModel } from '../../../models/video/video'
9import { VideoCommentModel } from '../../../models/video/video-comment' 7import { VideoCommentModel } from '../../../models/video/video-comment'
10import { areValidationErrors } from '../utils' 8import { areValidationErrors } from '../utils'
11import { Hooks } from '../../../lib/plugins/hooks' 9import { Hooks } from '../../../lib/plugins/hooks'
12import { isLocalVideoThreadAccepted, isLocalVideoCommentReplyAccepted, AcceptResult } from '../../../lib/moderation' 10import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation'
13import { doesVideoExist } from '../../../helpers/middlewares' 11import { doesVideoExist } from '../../../helpers/middlewares'
12import { MCommentOwner, MVideo, MVideoFullLight, MVideoId } from '../../../typings/models/video'
13import { MUser } from '@server/typings/models'
14 14
15const listVideoCommentThreadsValidator = [ 15const listVideoCommentThreadsValidator = [
16 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 16 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
@@ -34,7 +34,7 @@ const listVideoThreadCommentsValidator = [
34 34
35 if (areValidationErrors(req, res)) return 35 if (areValidationErrors(req, res)) return
36 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return 36 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
37 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return 37 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
38 38
39 return next() 39 return next()
40 } 40 }
@@ -49,8 +49,8 @@ const addVideoCommentThreadValidator = [
49 49
50 if (areValidationErrors(req, res)) return 50 if (areValidationErrors(req, res)) return
51 if (!await doesVideoExist(req.params.videoId, res)) return 51 if (!await doesVideoExist(req.params.videoId, res)) return
52 if (!isVideoCommentsEnabled(res.locals.video, res)) return 52 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
53 if (!await isVideoCommentAccepted(req, res, false)) return 53 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll,false)) return
54 54
55 return next() 55 return next()
56 } 56 }
@@ -66,9 +66,9 @@ const addVideoCommentReplyValidator = [
66 66
67 if (areValidationErrors(req, res)) return 67 if (areValidationErrors(req, res)) return
68 if (!await doesVideoExist(req.params.videoId, res)) return 68 if (!await doesVideoExist(req.params.videoId, res)) return
69 if (!isVideoCommentsEnabled(res.locals.video, res)) return 69 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
70 if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return 70 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
71 if (!await isVideoCommentAccepted(req, res, true)) return 71 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, true)) return
72 72
73 return next() 73 return next()
74 } 74 }
@@ -83,7 +83,7 @@ const videoCommentGetValidator = [
83 83
84 if (areValidationErrors(req, res)) return 84 if (areValidationErrors(req, res)) return
85 if (!await doesVideoExist(req.params.videoId, res, 'id')) return 85 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
86 if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return 86 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return
87 87
88 return next() 88 return next()
89 } 89 }
@@ -98,10 +98,10 @@ const removeVideoCommentValidator = [
98 98
99 if (areValidationErrors(req, res)) return 99 if (areValidationErrors(req, res)) return
100 if (!await doesVideoExist(req.params.videoId, res)) return 100 if (!await doesVideoExist(req.params.videoId, res)) return
101 if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return 101 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
102 102
103 // Check if the user who did the request is able to delete the video 103 // Check if the user who did the request is able to delete the video
104 if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoComment, res)) return 104 if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoCommentFull, res)) return
105 105
106 return next() 106 return next()
107 } 107 }
@@ -120,7 +120,7 @@ export {
120 120
121// --------------------------------------------------------------------------- 121// ---------------------------------------------------------------------------
122 122
123async function doesVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) { 123async function doesVideoCommentThreadExist (id: number, video: MVideoId, res: express.Response) {
124 const videoComment = await VideoCommentModel.loadById(id) 124 const videoComment = await VideoCommentModel.loadById(id)
125 125
126 if (!videoComment) { 126 if (!videoComment) {
@@ -151,7 +151,7 @@ async function doesVideoCommentThreadExist (id: number, video: VideoModel, res:
151 return true 151 return true
152} 152}
153 153
154async function doesVideoCommentExist (id: number, video: VideoModel, res: express.Response) { 154async function doesVideoCommentExist (id: number, video: MVideoId, res: express.Response) {
155 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) 155 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
156 156
157 if (!videoComment) { 157 if (!videoComment) {
@@ -170,11 +170,11 @@ async function doesVideoCommentExist (id: number, video: VideoModel, res: expres
170 return false 170 return false
171 } 171 }
172 172
173 res.locals.videoComment = videoComment 173 res.locals.videoCommentFull = videoComment
174 return true 174 return true
175} 175}
176 176
177function isVideoCommentsEnabled (video: VideoModel, res: express.Response) { 177function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
178 if (video.commentsEnabled !== true) { 178 if (video.commentsEnabled !== true) {
179 res.status(409) 179 res.status(409)
180 .json({ error: 'Video comments are disabled for this video.' }) 180 .json({ error: 'Video comments are disabled for this video.' })
@@ -186,7 +186,7 @@ function isVideoCommentsEnabled (video: VideoModel, res: express.Response) {
186 return true 186 return true
187} 187}
188 188
189function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCommentModel, res: express.Response) { 189function checkUserCanDeleteVideoComment (user: MUser, videoComment: MCommentOwner, res: express.Response) {
190 const account = videoComment.Account 190 const account = videoComment.Account
191 if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) { 191 if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) {
192 res.status(403) 192 res.status(403)
@@ -198,9 +198,9 @@ function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCom
198 return true 198 return true
199} 199}
200 200
201async function isVideoCommentAccepted (req: express.Request, res: express.Response, isReply: boolean) { 201async function isVideoCommentAccepted (req: express.Request, res: express.Response, video: MVideoFullLight, isReply: boolean) {
202 const acceptParameters = { 202 const acceptParameters = {
203 video: res.locals.video, 203 video,
204 commentBody: req.body, 204 commentBody: req.body,
205 user: res.locals.oauth.token.User 205 user: res.locals.oauth.token.User
206 } 206 }
@@ -208,7 +208,7 @@ async function isVideoCommentAccepted (req: express.Request, res: express.Respon
208 let acceptedResult: AcceptResult 208 let acceptedResult: AcceptResult
209 209
210 if (isReply) { 210 if (isReply) {
211 const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoComment }) 211 const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoCommentFull })
212 212
213 acceptedResult = await Hooks.wrapFun( 213 acceptedResult = await Hooks.wrapFun(
214 isLocalVideoCommentReplyAccepted, 214 isLocalVideoCommentReplyAccepted,
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index 5823795be..27ee62b1f 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -2,7 +2,6 @@ import * as express from 'express'
2import { body, param, query, ValidationChain } from 'express-validator' 2import { body, param, query, ValidationChain } from 'express-validator'
3import { UserRight, VideoPlaylistCreate, VideoPlaylistUpdate } from '../../../../shared' 3import { UserRight, VideoPlaylistCreate, VideoPlaylistUpdate } from '../../../../shared'
4import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
5import { UserModel } from '../../../models/account/user'
6import { areValidationErrors } from '../utils' 5import { areValidationErrors } from '../utils'
7import { isVideoImage } from '../../../helpers/custom-validators/videos' 6import { isVideoImage } from '../../../helpers/custom-validators/videos'
8import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' 7import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
@@ -22,13 +21,14 @@ import {
22 isVideoPlaylistTimestampValid, 21 isVideoPlaylistTimestampValid,
23 isVideoPlaylistTypeValid 22 isVideoPlaylistTypeValid
24} from '../../../helpers/custom-validators/video-playlists' 23} from '../../../helpers/custom-validators/video-playlists'
25import { VideoPlaylistModel } from '../../../models/video/video-playlist'
26import { cleanUpReqFiles } from '../../../helpers/express-utils' 24import { cleanUpReqFiles } from '../../../helpers/express-utils'
27import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' 25import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element'
28import { authenticatePromiseIfNeeded } from '../../oauth' 26import { authenticatePromiseIfNeeded } from '../../oauth'
29import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' 27import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
30import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' 28import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model'
31import { doesVideoChannelIdExist, doesVideoExist, doesVideoPlaylistExist } from '../../../helpers/middlewares' 29import { doesVideoChannelIdExist, doesVideoExist, doesVideoPlaylistExist, VideoPlaylistFetchType } from '../../../helpers/middlewares'
30import { MVideoPlaylist } from '../../../typings/models/video/video-playlist'
31import { MUserAccountId } from '@server/typings/models'
32 32
33const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ 33const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
34 body('displayName') 34 body('displayName')
@@ -67,9 +67,9 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([
67 67
68 if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return cleanUpReqFiles(req) 68 if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return cleanUpReqFiles(req)
69 69
70 const videoPlaylist = res.locals.videoPlaylist 70 const videoPlaylist = getPlaylist(res)
71 71
72 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { 72 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
73 return cleanUpReqFiles(req) 73 return cleanUpReqFiles(req)
74 } 74 }
75 75
@@ -110,13 +110,13 @@ const videoPlaylistsDeleteValidator = [
110 110
111 if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return 111 if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return
112 112
113 const videoPlaylist = res.locals.videoPlaylist 113 const videoPlaylist = getPlaylist(res)
114 if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { 114 if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) {
115 return res.status(400) 115 return res.status(400)
116 .json({ error: 'Cannot delete a watch later playlist.' }) 116 .json({ error: 'Cannot delete a watch later playlist.' })
117 } 117 }
118 118
119 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { 119 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
120 return 120 return
121 } 121 }
122 122
@@ -124,45 +124,47 @@ const videoPlaylistsDeleteValidator = [
124 } 124 }
125] 125]
126 126
127const videoPlaylistsGetValidator = [ 127const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => {
128 param('playlistId') 128 return [
129 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), 129 param('playlistId')
130 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
130 131
131 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 132 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
132 logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params }) 133 logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params })
133 134
134 if (areValidationErrors(req, res)) return 135 if (areValidationErrors(req, res)) return
135 136
136 if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return 137 if (!await doesVideoPlaylistExist(req.params.playlistId, res, fetchType)) return
137 138
138 const videoPlaylist = res.locals.videoPlaylist 139 const videoPlaylist = res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary
139 140
140 // Video is unlisted, check we used the uuid to fetch it 141 // Video is unlisted, check we used the uuid to fetch it
141 if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) { 142 if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) {
142 if (isUUIDValid(req.params.playlistId)) return next() 143 if (isUUIDValid(req.params.playlistId)) return next()
143 144
144 return res.status(404).end() 145 return res.status(404).end()
145 } 146 }
147
148 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
149 await authenticatePromiseIfNeeded(req, res)
146 150
147 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { 151 const user = res.locals.oauth ? res.locals.oauth.token.User : null
148 await authenticatePromiseIfNeeded(req, res)
149 152
150 const user = res.locals.oauth ? res.locals.oauth.token.User : null 153 if (
154 !user ||
155 (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST))
156 ) {
157 return res.status(403)
158 .json({ error: 'Cannot get this private video playlist.' })
159 }
151 160
152 if ( 161 return next()
153 !user ||
154 (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST))
155 ) {
156 return res.status(403)
157 .json({ error: 'Cannot get this private video playlist.' })
158 } 162 }
159 163
160 return next() 164 return next()
161 } 165 }
162 166 ]
163 return next() 167}
164 }
165]
166 168
167const videoPlaylistsAddVideoValidator = [ 169const videoPlaylistsAddVideoValidator = [
168 param('playlistId') 170 param('playlistId')
@@ -184,8 +186,8 @@ const videoPlaylistsAddVideoValidator = [
184 if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return 186 if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
185 if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return 187 if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
186 188
187 const videoPlaylist = res.locals.videoPlaylist 189 const videoPlaylist = getPlaylist(res)
188 const video = res.locals.video 190 const video = res.locals.onlyVideo
189 191
190 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id) 192 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id)
191 if (videoPlaylistElement) { 193 if (videoPlaylistElement) {
@@ -196,7 +198,7 @@ const videoPlaylistsAddVideoValidator = [
196 return 198 return
197 } 199 }
198 200
199 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) { 201 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) {
200 return 202 return
201 } 203 }
202 204
@@ -223,7 +225,7 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [
223 225
224 if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return 226 if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
225 227
226 const videoPlaylist = res.locals.videoPlaylist 228 const videoPlaylist = getPlaylist(res)
227 229
228 const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId) 230 const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId)
229 if (!videoPlaylistElement) { 231 if (!videoPlaylistElement) {
@@ -265,7 +267,7 @@ const videoPlaylistElementAPGetValidator = [
265 return res.status(403).end() 267 return res.status(403).end()
266 } 268 }
267 269
268 res.locals.videoPlaylistElement = videoPlaylistElement 270 res.locals.videoPlaylistElementAP = videoPlaylistElement
269 271
270 return next() 272 return next()
271 } 273 }
@@ -289,7 +291,7 @@ const videoPlaylistsReorderVideosValidator = [
289 291
290 if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return 292 if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
291 293
292 const videoPlaylist = res.locals.videoPlaylist 294 const videoPlaylist = getPlaylist(res)
293 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return 295 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return
294 296
295 const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id) 297 const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id)
@@ -388,7 +390,7 @@ function getCommonPlaylistEditAttributes () {
388 ] as (ValidationChain | express.Handler)[] 390 ] as (ValidationChain | express.Handler)[]
389} 391}
390 392
391function checkUserCanManageVideoPlaylist (user: UserModel, videoPlaylist: VideoPlaylistModel, right: UserRight, res: express.Response) { 393function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) {
392 if (videoPlaylist.isOwned() === false) { 394 if (videoPlaylist.isOwned() === false) {
393 res.status(403) 395 res.status(403)
394 .json({ error: 'Cannot manage video playlist of another server.' }) 396 .json({ error: 'Cannot manage video playlist of another server.' })
@@ -410,3 +412,7 @@ function checkUserCanManageVideoPlaylist (user: UserModel, videoPlaylist: VideoP
410 412
411 return true 413 return true
412} 414}
415
416function getPlaylist (res: express.Response) {
417 return res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary
418}
diff --git a/server/middlewares/validators/videos/video-shares.ts b/server/middlewares/validators/videos/video-shares.ts
index ace62be5c..20fc96243 100644
--- a/server/middlewares/validators/videos/video-shares.ts
+++ b/server/middlewares/validators/videos/video-shares.ts
@@ -16,7 +16,7 @@ const videosShareValidator = [
16 if (areValidationErrors(req, res)) return 16 if (areValidationErrors(req, res)) return
17 if (!await doesVideoExist(req.params.id, res)) return 17 if (!await doesVideoExist(req.params.id, res)) return
18 18
19 const video = res.locals.video 19 const video = res.locals.videoAll
20 20
21 const share = await VideoShareModel.load(req.params.actorId, video.id) 21 const share = await VideoShareModel.load(req.params.actorId, video.id)
22 if (!share) { 22 if (!share) {
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index af06f3c62..1449903b7 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -37,13 +37,14 @@ import { VideoModel } from '../../../models/video/video'
37import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership' 37import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership'
38import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' 38import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model'
39import { AccountModel } from '../../../models/account/account' 39import { AccountModel } from '../../../models/account/account'
40import { VideoFetchType } from '../../../helpers/video'
41import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' 40import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search'
42import { getServerActor } from '../../../helpers/utils' 41import { getServerActor } from '../../../helpers/utils'
43import { CONFIG } from '../../../initializers/config' 42import { CONFIG } from '../../../initializers/config'
44import { isLocalVideoAccepted } from '../../../lib/moderation' 43import { isLocalVideoAccepted } from '../../../lib/moderation'
45import { Hooks } from '../../../lib/plugins/hooks' 44import { Hooks } from '../../../lib/plugins/hooks'
46import { checkUserCanManageVideo, doesVideoChannelOfAccountExist, doesVideoExist } from '../../../helpers/middlewares' 45import { checkUserCanManageVideo, doesVideoChannelOfAccountExist, doesVideoExist } from '../../../helpers/middlewares'
46import { MVideoFullLight } from '@server/typings/models'
47import { getVideoWithAttributes } from '../../../helpers/video'
47 48
48const videosAddValidator = getCommonVideoEditAttributes().concat([ 49const videosAddValidator = getCommonVideoEditAttributes().concat([
49 body('videofile') 50 body('videofile')
@@ -113,7 +114,7 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([
113 114
114 // Check if the user who did the request is able to update the video 115 // Check if the user who did the request is able to update the video
115 const user = res.locals.oauth.token.User 116 const user = res.locals.oauth.token.User
116 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) 117 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
117 118
118 if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) 119 if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
119 120
@@ -122,7 +123,7 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([
122]) 123])
123 124
124async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) { 125async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) {
125 const video = res.locals.video 126 const video = getVideoWithAttributes(res)
126 127
127 // Anybody can watch local videos 128 // Anybody can watch local videos
128 if (video.isOwned() === true) return next() 129 if (video.isOwned() === true) return next()
@@ -146,7 +147,7 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R
146 }) 147 })
147} 148}
148 149
149const videosCustomGetValidator = (fetchType: VideoFetchType) => { 150const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video-with-rights') => {
150 return [ 151 return [
151 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 152 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
152 153
@@ -156,10 +157,11 @@ const videosCustomGetValidator = (fetchType: VideoFetchType) => {
156 if (areValidationErrors(req, res)) return 157 if (areValidationErrors(req, res)) return
157 if (!await doesVideoExist(req.params.id, res, fetchType)) return 158 if (!await doesVideoExist(req.params.id, res, fetchType)) return
158 159
159 const video = res.locals.video 160 const video = getVideoWithAttributes(res)
161 const videoAll = video as MVideoFullLight
160 162
161 // Video private or blacklisted 163 // Video private or blacklisted
162 if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) { 164 if (video.privacy === VideoPrivacy.PRIVATE || videoAll.VideoBlacklist) {
163 await authenticatePromiseIfNeeded(req, res) 165 await authenticatePromiseIfNeeded(req, res)
164 166
165 const user = res.locals.oauth ? res.locals.oauth.token.User : null 167 const user = res.locals.oauth ? res.locals.oauth.token.User : null
@@ -167,7 +169,7 @@ const videosCustomGetValidator = (fetchType: VideoFetchType) => {
167 // Only the owner or a user that have blacklist rights can see the video 169 // Only the owner or a user that have blacklist rights can see the video
168 if ( 170 if (
169 !user || 171 !user ||
170 (video.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) 172 (videoAll.VideoChannel && videoAll.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST))
171 ) { 173 ) {
172 return res.status(403) 174 return res.status(403)
173 .json({ error: 'Cannot get this private or blacklisted video.' }) 175 .json({ error: 'Cannot get this private or blacklisted video.' })
@@ -202,7 +204,7 @@ const videosRemoveValidator = [
202 if (!await doesVideoExist(req.params.id, res)) return 204 if (!await doesVideoExist(req.params.id, res)) return
203 205
204 // Check if the user who did the request is able to delete the video 206 // Check if the user who did the request is able to delete the video
205 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return 207 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.REMOVE_ANY_VIDEO, res)) return
206 208
207 return next() 209 return next()
208 } 210 }
@@ -218,7 +220,7 @@ const videosChangeOwnershipValidator = [
218 if (!await doesVideoExist(req.params.videoId, res)) return 220 if (!await doesVideoExist(req.params.videoId, res)) return
219 221
220 // Check if the user who did the request is able to change the ownership of the video 222 // Check if the user who did the request is able to change the ownership of the video
221 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return 223 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return
222 224
223 const nextOwner = await AccountModel.loadLocalByName(req.body.username) 225 const nextOwner = await AccountModel.loadLocalByName(req.body.username)
224 if (!nextOwner) { 226 if (!nextOwner) {
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts
index d7cfe17f0..d50e6527f 100644
--- a/server/middlewares/validators/webfinger.ts
+++ b/server/middlewares/validators/webfinger.ts
@@ -18,6 +18,7 @@ const webfingerValidator = [
18 const nameWithHost = getHostWithPort(req.query.resource.substr(5)) 18 const nameWithHost = getHostWithPort(req.query.resource.substr(5))
19 const [ name ] = nameWithHost.split('@') 19 const [ name ] = nameWithHost.split('@')
20 20
21 // FIXME: we don't need the full actor
21 const actor = await ActorModel.loadLocalByName(name) 22 const actor = await ActorModel.loadLocalByName(name)
22 if (!actor) { 23 if (!actor) {
23 return res.status(404) 24 return res.status(404)
@@ -25,7 +26,7 @@ const webfingerValidator = [
25 .end() 26 .end()
26 } 27 }
27 28
28 res.locals.actor = actor 29 res.locals.actorFull = actor
29 return next() 30 return next()
30 } 31 }
31] 32]
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts
index d5746ad76..8bcaca828 100644
--- a/server/models/account/account-blocklist.ts
+++ b/server/models/account/account-blocklist.ts
@@ -3,6 +3,8 @@ import { AccountModel } from './account'
3import { getSort } from '../utils' 3import { getSort } from '../utils'
4import { AccountBlock } from '../../../shared/models/blocklist' 4import { AccountBlock } from '../../../shared/models/blocklist'
5import { Op } from 'sequelize' 5import { Op } from 'sequelize'
6import * as Bluebird from 'bluebird'
7import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/typings/models'
6 8
7enum ScopeNames { 9enum ScopeNames {
8 WITH_ACCOUNTS = 'WITH_ACCOUNTS' 10 WITH_ACCOUNTS = 'WITH_ACCOUNTS'
@@ -103,7 +105,7 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
103 }) 105 })
104 } 106 }
105 107
106 static loadByAccountAndTarget (accountId: number, targetAccountId: number) { 108 static loadByAccountAndTarget (accountId: number, targetAccountId: number): Bluebird<MAccountBlocklist> {
107 const query = { 109 const query = {
108 where: { 110 where: {
109 accountId, 111 accountId,
@@ -126,13 +128,13 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
126 128
127 return AccountBlocklistModel 129 return AccountBlocklistModel
128 .scope([ ScopeNames.WITH_ACCOUNTS ]) 130 .scope([ ScopeNames.WITH_ACCOUNTS ])
129 .findAndCountAll(query) 131 .findAndCountAll<MAccountBlocklistAccounts>(query)
130 .then(({ rows, count }) => { 132 .then(({ rows, count }) => {
131 return { total: count, data: rows } 133 return { total: count, data: rows }
132 }) 134 })
133 } 135 }
134 136
135 toFormattedJSON (): AccountBlock { 137 toFormattedJSON (this: MAccountBlocklistFormattable): AccountBlock {
136 return { 138 return {
137 byAccount: this.ByAccount.toFormattedJSON(), 139 byAccount: this.ByAccount.toFormattedJSON(),
138 blockedAccount: this.BlockedAccount.toFormattedJSON(), 140 blockedAccount: this.BlockedAccount.toFormattedJSON(),
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts
index 4bd8114cf..a6edbeee8 100644
--- a/server/models/account/account-video-rate.ts
+++ b/server/models/account/account-video-rate.ts
@@ -10,6 +10,13 @@ import { buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils'
10import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 10import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
11import { AccountVideoRate } from '../../../shared' 11import { AccountVideoRate } from '../../../shared'
12import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' 12import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel'
13import * as Bluebird from 'bluebird'
14import {
15 MAccountVideoRate,
16 MAccountVideoRateAccountUrl,
17 MAccountVideoRateAccountVideo,
18 MAccountVideoRateFormattable
19} from '@server/typings/models/video/video-rate'
13 20
14/* 21/*
15 Account rates per video. 22 Account rates per video.
@@ -77,7 +84,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
77 }) 84 })
78 Account: AccountModel 85 Account: AccountModel
79 86
80 static load (accountId: number, videoId: number, transaction?: Transaction) { 87 static load (accountId: number, videoId: number, transaction?: Transaction): Bluebird<MAccountVideoRate> {
81 const options: FindOptions = { 88 const options: FindOptions = {
82 where: { 89 where: {
83 accountId, 90 accountId,
@@ -89,7 +96,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
89 return AccountVideoRateModel.findOne(options) 96 return AccountVideoRateModel.findOne(options)
90 } 97 }
91 98
92 static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, transaction?: Transaction) { 99 static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, t?: Transaction): Bluebird<MAccountVideoRate> {
93 const options: FindOptions = { 100 const options: FindOptions = {
94 where: { 101 where: {
95 [ Op.or]: [ 102 [ Op.or]: [
@@ -103,7 +110,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
103 ] 110 ]
104 } 111 }
105 } 112 }
106 if (transaction) options.transaction = transaction 113 if (t) options.transaction = t
107 114
108 return AccountVideoRateModel.findOne(options) 115 return AccountVideoRateModel.findOne(options)
109 } 116 }
@@ -140,7 +147,12 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
140 return AccountVideoRateModel.findAndCountAll(query) 147 return AccountVideoRateModel.findAndCountAll(query)
141 } 148 }
142 149
143 static loadLocalAndPopulateVideo (rateType: VideoRateType, accountName: string, videoId: number, transaction?: Transaction) { 150 static loadLocalAndPopulateVideo (
151 rateType: VideoRateType,
152 accountName: string,
153 videoId: number,
154 t?: Transaction
155 ): Bluebird<MAccountVideoRateAccountVideo> {
144 const options: FindOptions = { 156 const options: FindOptions = {
145 where: { 157 where: {
146 videoId, 158 videoId,
@@ -152,7 +164,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
152 required: true, 164 required: true,
153 include: [ 165 include: [
154 { 166 {
155 attributes: [ 'id', 'url', 'preferredUsername' ], 167 attributes: [ 'id', 'url', 'followersUrl', 'preferredUsername' ],
156 model: ActorModel.unscoped(), 168 model: ActorModel.unscoped(),
157 required: true, 169 required: true,
158 where: { 170 where: {
@@ -167,7 +179,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
167 } 179 }
168 ] 180 ]
169 } 181 }
170 if (transaction) options.transaction = transaction 182 if (t) options.transaction = t
171 183
172 return AccountVideoRateModel.findOne(options) 184 return AccountVideoRateModel.findOne(options)
173 } 185 }
@@ -208,7 +220,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
208 ] 220 ]
209 } 221 }
210 222
211 return AccountVideoRateModel.findAndCountAll(query) 223 return AccountVideoRateModel.findAndCountAll<MAccountVideoRateAccountUrl>(query)
212 } 224 }
213 225
214 static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) { 226 static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) {
@@ -241,7 +253,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
241 }) 253 })
242 } 254 }
243 255
244 toFormattedJSON (): AccountVideoRate { 256 toFormattedJSON (this: MAccountVideoRateFormattable): AccountVideoRate {
245 return { 257 return {
246 video: this.Video.toFormattedJSON(), 258 video: this.Video.toFormattedJSON(),
247 rating: this.type 259 rating: this.type
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index 4dc412301..394a55f5e 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -3,7 +3,8 @@ import {
3 BeforeDestroy, 3 BeforeDestroy,
4 BelongsTo, 4 BelongsTo,
5 Column, 5 Column,
6 CreatedAt, DataType, 6 CreatedAt,
7 DataType,
7 Default, 8 Default,
8 DefaultScope, 9 DefaultScope,
9 ForeignKey, 10 ForeignKey,
@@ -31,6 +32,8 @@ import { FindOptions, IncludeOptions, Op, Transaction, WhereOptions } from 'sequ
31import { AccountBlocklistModel } from './account-blocklist' 32import { AccountBlocklistModel } from './account-blocklist'
32import { ServerBlocklistModel } from '../server/server-blocklist' 33import { ServerBlocklistModel } from '../server/server-blocklist'
33import { ActorFollowModel } from '../activitypub/actor-follow' 34import { ActorFollowModel } from '../activitypub/actor-follow'
35import { MAccountActor, MAccountDefault, MAccountSummaryFormattable, MAccountFormattable, MAccountAP } from '../../typings/models'
36import * as Bluebird from 'bluebird'
34 37
35export enum ScopeNames { 38export enum ScopeNames {
36 SUMMARY = 'SUMMARY' 39 SUMMARY = 'SUMMARY'
@@ -229,11 +232,11 @@ export class AccountModel extends Model<AccountModel> {
229 return undefined 232 return undefined
230 } 233 }
231 234
232 static load (id: number, transaction?: Transaction) { 235 static load (id: number, transaction?: Transaction): Bluebird<MAccountDefault> {
233 return AccountModel.findByPk(id, { transaction }) 236 return AccountModel.findByPk(id, { transaction })
234 } 237 }
235 238
236 static loadByNameWithHost (nameWithHost: string) { 239 static loadByNameWithHost (nameWithHost: string): Bluebird<MAccountDefault> {
237 const [ accountName, host ] = nameWithHost.split('@') 240 const [ accountName, host ] = nameWithHost.split('@')
238 241
239 if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName) 242 if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName)
@@ -241,7 +244,7 @@ export class AccountModel extends Model<AccountModel> {
241 return AccountModel.loadByNameAndHost(accountName, host) 244 return AccountModel.loadByNameAndHost(accountName, host)
242 } 245 }
243 246
244 static loadLocalByName (name: string) { 247 static loadLocalByName (name: string): Bluebird<MAccountDefault> {
245 const query = { 248 const query = {
246 where: { 249 where: {
247 [ Op.or ]: [ 250 [ Op.or ]: [
@@ -271,7 +274,7 @@ export class AccountModel extends Model<AccountModel> {
271 return AccountModel.findOne(query) 274 return AccountModel.findOne(query)
272 } 275 }
273 276
274 static loadByNameAndHost (name: string, host: string) { 277 static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> {
275 const query = { 278 const query = {
276 include: [ 279 include: [
277 { 280 {
@@ -296,7 +299,7 @@ export class AccountModel extends Model<AccountModel> {
296 return AccountModel.findOne(query) 299 return AccountModel.findOne(query)
297 } 300 }
298 301
299 static loadByUrl (url: string, transaction?: Transaction) { 302 static loadByUrl (url: string, transaction?: Transaction): Bluebird<MAccountDefault> {
300 const query = { 303 const query = {
301 include: [ 304 include: [
302 { 305 {
@@ -329,7 +332,7 @@ export class AccountModel extends Model<AccountModel> {
329 }) 332 })
330 } 333 }
331 334
332 static listLocalsForSitemap (sort: string) { 335 static listLocalsForSitemap (sort: string): Bluebird<MAccountActor[]> {
333 const query = { 336 const query = {
334 attributes: [ ], 337 attributes: [ ],
335 offset: 0, 338 offset: 0,
@@ -350,7 +353,7 @@ export class AccountModel extends Model<AccountModel> {
350 .findAll(query) 353 .findAll(query)
351 } 354 }
352 355
353 toFormattedJSON (): Account { 356 toFormattedJSON (this: MAccountFormattable): Account {
354 const actor = this.Actor.toFormattedJSON() 357 const actor = this.Actor.toFormattedJSON()
355 const account = { 358 const account = {
356 id: this.id, 359 id: this.id,
@@ -364,8 +367,8 @@ export class AccountModel extends Model<AccountModel> {
364 return Object.assign(actor, account) 367 return Object.assign(actor, account)
365 } 368 }
366 369
367 toFormattedSummaryJSON (): AccountSummary { 370 toFormattedSummaryJSON (this: MAccountSummaryFormattable): AccountSummary {
368 const actor = this.Actor.toFormattedJSON() 371 const actor = this.Actor.toFormattedSummaryJSON()
369 372
370 return { 373 return {
371 id: this.id, 374 id: this.id,
@@ -377,7 +380,7 @@ export class AccountModel extends Model<AccountModel> {
377 } 380 }
378 } 381 }
379 382
380 toActivityPubObject () { 383 toActivityPubObject (this: MAccountAP) {
381 const obj = this.Actor.toActivityPubObject(this.name, 'Account') 384 const obj = this.Actor.toActivityPubObject(this.name, 'Account')
382 385
383 return Object.assign(obj, { 386 return Object.assign(obj, {
diff --git a/server/models/account/user-notification-setting.ts b/server/models/account/user-notification-setting.ts
index c2fbc6d23..1506295cf 100644
--- a/server/models/account/user-notification-setting.ts
+++ b/server/models/account/user-notification-setting.ts
@@ -17,6 +17,7 @@ import { UserModel } from './user'
17import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' 17import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
18import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' 18import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
19import { clearCacheByUserId } from '../../lib/oauth-model' 19import { clearCacheByUserId } from '../../lib/oauth-model'
20import { MNotificationSettingFormattable } from '@server/typings/models'
20 21
21@Table({ 22@Table({
22 tableName: 'userNotificationSetting', 23 tableName: 'userNotificationSetting',
@@ -152,7 +153,7 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
152 return clearCacheByUserId(instance.userId) 153 return clearCacheByUserId(instance.userId)
153 } 154 }
154 155
155 toFormattedJSON (): UserNotificationSetting { 156 toFormattedJSON (this: MNotificationSettingFormattable): UserNotificationSetting {
156 return { 157 return {
157 newCommentOnMyVideo: this.newCommentOnMyVideo, 158 newCommentOnMyVideo: this.newCommentOnMyVideo,
158 newVideoFromSubscription: this.newVideoFromSubscription, 159 newVideoFromSubscription: this.newVideoFromSubscription,
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts
index f38cd7e78..9b13a8376 100644
--- a/server/models/account/user-notification.ts
+++ b/server/models/account/user-notification.ts
@@ -16,6 +16,7 @@ import { ActorModel } from '../activitypub/actor'
16import { ActorFollowModel } from '../activitypub/actor-follow' 16import { ActorFollowModel } from '../activitypub/actor-follow'
17import { AvatarModel } from '../avatar/avatar' 17import { AvatarModel } from '../avatar/avatar'
18import { ServerModel } from '../server/server' 18import { ServerModel } from '../server/server'
19import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/typings/models/user'
19 20
20enum ScopeNames { 21enum ScopeNames {
21 WITH_ALL = 'WITH_ALL' 22 WITH_ALL = 'WITH_ALL'
@@ -371,7 +372,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
371 return UserNotificationModel.update({ read: true }, query) 372 return UserNotificationModel.update({ read: true }, query)
372 } 373 }
373 374
374 toFormattedJSON (): UserNotification { 375 toFormattedJSON (this: UserNotificationModelForApi): UserNotification {
375 const video = this.Video 376 const video = this.Video
376 ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) }) 377 ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) })
377 : undefined 378 : undefined
@@ -436,7 +437,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
436 } 437 }
437 } 438 }
438 439
439 private formatVideo (video: VideoModel) { 440 formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) {
440 return { 441 return {
441 id: video.id, 442 id: video.id,
442 uuid: video.uuid, 443 uuid: video.uuid,
@@ -444,7 +445,10 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
444 } 445 }
445 } 446 }
446 447
447 private formatActor (accountOrChannel: AccountModel | VideoChannelModel) { 448 formatActor (
449 this: UserNotificationModelForApi,
450 accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor
451 ) {
448 const avatar = accountOrChannel.Actor.Avatar 452 const avatar = accountOrChannel.Actor.Avatar
449 ? { path: accountOrChannel.Actor.Avatar.getStaticPath() } 453 ? { path: accountOrChannel.Actor.Avatar.getStaticPath() }
450 : undefined 454 : undefined
diff --git a/server/models/account/user-video-history.ts b/server/models/account/user-video-history.ts
index a862fc45f..3fe4c8db1 100644
--- a/server/models/account/user-video-history.ts
+++ b/server/models/account/user-video-history.ts
@@ -1,7 +1,8 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { VideoModel } from '../video/video' 2import { VideoModel } from '../video/video'
3import { UserModel } from './user' 3import { UserModel } from './user'
4import { Transaction, Op, DestroyOptions } from 'sequelize' 4import { DestroyOptions, Op, Transaction } from 'sequelize'
5import { MUserAccountId, MUserId } from '@server/typings/models'
5 6
6@Table({ 7@Table({
7 tableName: 'userVideoHistory', 8 tableName: 'userVideoHistory',
@@ -54,7 +55,7 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> {
54 }) 55 })
55 User: UserModel 56 User: UserModel
56 57
57 static listForApi (user: UserModel, start: number, count: number) { 58 static listForApi (user: MUserAccountId, start: number, count: number) {
58 return VideoModel.listForApi({ 59 return VideoModel.listForApi({
59 start, 60 start,
60 count, 61 count,
@@ -67,7 +68,7 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> {
67 }) 68 })
68 } 69 }
69 70
70 static removeUserHistoryBefore (user: UserModel, beforeDate: string, t: Transaction) { 71 static removeUserHistoryBefore (user: MUserId, beforeDate: string, t: Transaction) {
71 const query: DestroyOptions = { 72 const query: DestroyOptions = {
72 where: { 73 where: {
73 userId: user.id 74 userId: user.id
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 0041bf577..616dd603c 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -54,6 +54,14 @@ import { VideoImportModel } from '../video/video-import'
54import { UserAdminFlag } from '../../../shared/models/users/user-flag.model' 54import { UserAdminFlag } from '../../../shared/models/users/user-flag.model'
55import { isThemeNameValid } from '../../helpers/custom-validators/plugins' 55import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
56import { getThemeOrDefault } from '../../lib/plugins/theme-utils' 56import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
57import * as Bluebird from 'bluebird'
58import {
59 MUserDefault,
60 MUserFormattable,
61 MUserId,
62 MUserNotifSettingChannelDefault,
63 MUserWithNotificationSetting
64} from '@server/typings/models'
57 65
58enum ScopeNames { 66enum ScopeNames {
59 WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' 67 WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
@@ -303,7 +311,7 @@ export class UserModel extends Model<UserModel> {
303 }) 311 })
304 } 312 }
305 313
306 static listWithRight (right: UserRight) { 314 static listWithRight (right: UserRight): Bluebird<MUserDefault[]> {
307 const roles = Object.keys(USER_ROLE_LABELS) 315 const roles = Object.keys(USER_ROLE_LABELS)
308 .map(k => parseInt(k, 10) as UserRole) 316 .map(k => parseInt(k, 10) as UserRole)
309 .filter(role => hasUserRight(role, right)) 317 .filter(role => hasUserRight(role, right))
@@ -319,7 +327,7 @@ export class UserModel extends Model<UserModel> {
319 return UserModel.findAll(query) 327 return UserModel.findAll(query)
320 } 328 }
321 329
322 static listUserSubscribersOf (actorId: number) { 330 static listUserSubscribersOf (actorId: number): Bluebird<MUserWithNotificationSetting[]> {
323 const query = { 331 const query = {
324 include: [ 332 include: [
325 { 333 {
@@ -358,7 +366,7 @@ export class UserModel extends Model<UserModel> {
358 return UserModel.unscoped().findAll(query) 366 return UserModel.unscoped().findAll(query)
359 } 367 }
360 368
361 static listByUsernames (usernames: string[]) { 369 static listByUsernames (usernames: string[]): Bluebird<MUserDefault[]> {
362 const query = { 370 const query = {
363 where: { 371 where: {
364 username: usernames 372 username: usernames
@@ -368,11 +376,11 @@ export class UserModel extends Model<UserModel> {
368 return UserModel.findAll(query) 376 return UserModel.findAll(query)
369 } 377 }
370 378
371 static loadById (id: number) { 379 static loadById (id: number): Bluebird<MUserDefault> {
372 return UserModel.findByPk(id) 380 return UserModel.findByPk(id)
373 } 381 }
374 382
375 static loadByUsername (username: string) { 383 static loadByUsername (username: string): Bluebird<MUserDefault> {
376 const query = { 384 const query = {
377 where: { 385 where: {
378 username: { [ Op.iLike ]: username } 386 username: { [ Op.iLike ]: username }
@@ -382,7 +390,7 @@ export class UserModel extends Model<UserModel> {
382 return UserModel.findOne(query) 390 return UserModel.findOne(query)
383 } 391 }
384 392
385 static loadByUsernameAndPopulateChannels (username: string) { 393 static loadByUsernameAndPopulateChannels (username: string): Bluebird<MUserNotifSettingChannelDefault> {
386 const query = { 394 const query = {
387 where: { 395 where: {
388 username: { [ Op.iLike ]: username } 396 username: { [ Op.iLike ]: username }
@@ -392,7 +400,7 @@ export class UserModel extends Model<UserModel> {
392 return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query) 400 return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query)
393 } 401 }
394 402
395 static loadByEmail (email: string) { 403 static loadByEmail (email: string): Bluebird<MUserDefault> {
396 const query = { 404 const query = {
397 where: { 405 where: {
398 email 406 email
@@ -402,7 +410,7 @@ export class UserModel extends Model<UserModel> {
402 return UserModel.findOne(query) 410 return UserModel.findOne(query)
403 } 411 }
404 412
405 static loadByUsernameOrEmail (username: string, email?: string) { 413 static loadByUsernameOrEmail (username: string, email?: string): Bluebird<MUserDefault> {
406 if (!email) email = username 414 if (!email) email = username
407 415
408 const query = { 416 const query = {
@@ -414,7 +422,7 @@ export class UserModel extends Model<UserModel> {
414 return UserModel.findOne(query) 422 return UserModel.findOne(query)
415 } 423 }
416 424
417 static loadByVideoId (videoId: number) { 425 static loadByVideoId (videoId: number): Bluebird<MUserDefault> {
418 const query = { 426 const query = {
419 include: [ 427 include: [
420 { 428 {
@@ -445,7 +453,7 @@ export class UserModel extends Model<UserModel> {
445 return UserModel.findOne(query) 453 return UserModel.findOne(query)
446 } 454 }
447 455
448 static loadByVideoImportId (videoImportId: number) { 456 static loadByVideoImportId (videoImportId: number): Bluebird<MUserDefault> {
449 const query = { 457 const query = {
450 include: [ 458 include: [
451 { 459 {
@@ -462,7 +470,7 @@ export class UserModel extends Model<UserModel> {
462 return UserModel.findOne(query) 470 return UserModel.findOne(query)
463 } 471 }
464 472
465 static loadByChannelActorId (videoChannelActorId: number) { 473 static loadByChannelActorId (videoChannelActorId: number): Bluebird<MUserDefault> {
466 const query = { 474 const query = {
467 include: [ 475 include: [
468 { 476 {
@@ -486,7 +494,7 @@ export class UserModel extends Model<UserModel> {
486 return UserModel.findOne(query) 494 return UserModel.findOne(query)
487 } 495 }
488 496
489 static loadByAccountActorId (accountActorId: number) { 497 static loadByAccountActorId (accountActorId: number): Bluebird<MUserDefault> {
490 const query = { 498 const query = {
491 include: [ 499 include: [
492 { 500 {
@@ -503,7 +511,7 @@ export class UserModel extends Model<UserModel> {
503 return UserModel.findOne(query) 511 return UserModel.findOne(query)
504 } 512 }
505 513
506 static getOriginalVideoFileTotalFromUser (user: UserModel) { 514 static getOriginalVideoFileTotalFromUser (user: MUserId) {
507 // Don't use sequelize because we need to use a sub query 515 // Don't use sequelize because we need to use a sub query
508 const query = UserModel.generateUserQuotaBaseSQL() 516 const query = UserModel.generateUserQuotaBaseSQL()
509 517
@@ -511,7 +519,7 @@ export class UserModel extends Model<UserModel> {
511 } 519 }
512 520
513 // Returns cumulative size of all video files uploaded in the last 24 hours. 521 // Returns cumulative size of all video files uploaded in the last 24 hours.
514 static getOriginalVideoFileTotalDailyFromUser (user: UserModel) { 522 static getOriginalVideoFileTotalDailyFromUser (user: MUserId) {
515 // Don't use sequelize because we need to use a sub query 523 // Don't use sequelize because we need to use a sub query
516 const query = UserModel.generateUserQuotaBaseSQL('"video"."createdAt" > now() - interval \'24 hours\'') 524 const query = UserModel.generateUserQuotaBaseSQL('"video"."createdAt" > now() - interval \'24 hours\'')
517 525
@@ -552,7 +560,9 @@ export class UserModel extends Model<UserModel> {
552 return comparePassword(password, this.password) 560 return comparePassword(password, this.password)
553 } 561 }
554 562
555 toFormattedJSON (parameters: { withAdminFlags?: boolean } = {}): User { 563 toSummaryJSON
564
565 toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User {
556 const videoQuotaUsed = this.get('videoQuotaUsed') 566 const videoQuotaUsed = this.get('videoQuotaUsed')
557 const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') 567 const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily')
558 568
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts
index 51b09e09b..c8b3aae9f 100644
--- a/server/models/activitypub/actor-follow.ts
+++ b/server/models/activitypub/actor-follow.ts
@@ -27,7 +27,14 @@ import { createSafeIn, getSort } from '../utils'
27import { ActorModel, unusedActorAttributesForAPI } from './actor' 27import { ActorModel, unusedActorAttributesForAPI } from './actor'
28import { VideoChannelModel } from '../video/video-channel' 28import { VideoChannelModel } from '../video/video-channel'
29import { AccountModel } from '../account/account' 29import { AccountModel } from '../account/account'
30import { IncludeOptions, Op, Transaction, QueryTypes } from 'sequelize' 30import { IncludeOptions, Op, QueryTypes, Transaction } from 'sequelize'
31import {
32 MActorFollowActorsDefault,
33 MActorFollowActorsDefaultSubscription,
34 MActorFollowFollowingHost,
35 MActorFollowFormattable,
36 MActorFollowSubscriptions
37} from '@server/typings/models'
31 38
32@Table({ 39@Table({
33 tableName: 'actorFollow', 40 tableName: 'actorFollow',
@@ -143,7 +150,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
143 if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved) 150 if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved)
144 } 151 }
145 152
146 static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction) { 153 static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction): Bluebird<MActorFollowActorsDefault> {
147 const query = { 154 const query = {
148 where: { 155 where: {
149 actorId, 156 actorId,
@@ -167,7 +174,12 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
167 return ActorFollowModel.findOne(query) 174 return ActorFollowModel.findOne(query)
168 } 175 }
169 176
170 static loadByActorAndTargetNameAndHostForAPI (actorId: number, targetName: string, targetHost: string, t?: Transaction) { 177 static loadByActorAndTargetNameAndHostForAPI (
178 actorId: number,
179 targetName: string,
180 targetHost: string,
181 t?: Transaction
182 ): Bluebird<MActorFollowActorsDefaultSubscription> {
171 const actorFollowingPartInclude: IncludeOptions = { 183 const actorFollowingPartInclude: IncludeOptions = {
172 model: ActorModel, 184 model: ActorModel,
173 required: true, 185 required: true,
@@ -220,7 +232,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
220 }) 232 })
221 } 233 }
222 234
223 static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]) { 235 static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]): Bluebird<MActorFollowFollowingHost[]> {
224 const whereTab = targets 236 const whereTab = targets
225 .map(t => { 237 .map(t => {
226 if (t.host) { 238 if (t.host) {
@@ -314,7 +326,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
314 ] 326 ]
315 } 327 }
316 328
317 return ActorFollowModel.findAndCountAll(query) 329 return ActorFollowModel.findAndCountAll<MActorFollowActorsDefault>(query)
318 .then(({ rows, count }) => { 330 .then(({ rows, count }) => {
319 return { 331 return {
320 data: rows, 332 data: rows,
@@ -357,7 +369,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
357 ] 369 ]
358 } 370 }
359 371
360 return ActorFollowModel.findAndCountAll(query) 372 return ActorFollowModel.findAndCountAll<MActorFollowActorsDefault>(query)
361 .then(({ rows, count }) => { 373 .then(({ rows, count }) => {
362 return { 374 return {
363 data: rows, 375 data: rows,
@@ -414,7 +426,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
414 ] 426 ]
415 } 427 }
416 428
417 return ActorFollowModel.findAndCountAll(query) 429 return ActorFollowModel.findAndCountAll<MActorFollowSubscriptions>(query)
418 .then(({ rows, count }) => { 430 .then(({ rows, count }) => {
419 return { 431 return {
420 data: rows.map(r => r.ActorFollowing.VideoChannel), 432 data: rows.map(r => r.ActorFollowing.VideoChannel),
@@ -569,7 +581,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
569 return ActorFollowModel.findAll(query) 581 return ActorFollowModel.findAll(query)
570 } 582 }
571 583
572 toFormattedJSON (): ActorFollow { 584 toFormattedJSON (this: MActorFollowFormattable): ActorFollow {
573 const follower = this.ActorFollower.toFormattedJSON() 585 const follower = this.ActorFollower.toFormattedJSON()
574 const following = this.ActorFollowing.toFormattedJSON() 586 const following = this.ActorFollowing.toFormattedJSON()
575 587
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts
index 9cc53f78a..67a1b5bc1 100644
--- a/server/models/activitypub/actor.ts
+++ b/server/models/activitypub/actor.ts
@@ -36,6 +36,18 @@ import { isOutdated, throwIfNotValid } from '../utils'
36import { VideoChannelModel } from '../video/video-channel' 36import { VideoChannelModel } from '../video/video-channel'
37import { ActorFollowModel } from './actor-follow' 37import { ActorFollowModel } from './actor-follow'
38import { VideoModel } from '../video/video' 38import { VideoModel } from '../video/video'
39import {
40 MActor,
41 MActorAccountChannelId,
42 MActorAP,
43 MActorFormattable,
44 MActorFull,
45 MActorHost,
46 MActorRedundancyAllowedOpt,
47 MActorServer,
48 MActorSummaryFormattable
49} from '../../typings/models'
50import * as Bluebird from 'bluebird'
39 51
40enum ScopeNames { 52enum ScopeNames {
41 FULL = 'FULL' 53 FULL = 'FULL'
@@ -163,8 +175,8 @@ export class ActorModel extends Model<ActorModel> {
163 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) 175 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
164 inboxUrl: string 176 inboxUrl: string
165 177
166 @AllowNull(false) 178 @AllowNull(true)
167 @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url')) 179 @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url', true))
168 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) 180 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
169 outboxUrl: string 181 outboxUrl: string
170 182
@@ -173,13 +185,13 @@ export class ActorModel extends Model<ActorModel> {
173 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) 185 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
174 sharedInboxUrl: string 186 sharedInboxUrl: string
175 187
176 @AllowNull(false) 188 @AllowNull(true)
177 @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url')) 189 @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url', true))
178 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) 190 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
179 followersUrl: string 191 followersUrl: string
180 192
181 @AllowNull(false) 193 @AllowNull(true)
182 @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url')) 194 @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url', true))
183 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) 195 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
184 followingUrl: string 196 followingUrl: string
185 197
@@ -252,11 +264,15 @@ export class ActorModel extends Model<ActorModel> {
252 }) 264 })
253 VideoChannel: VideoChannelModel 265 VideoChannel: VideoChannelModel
254 266
255 static load (id: number) { 267 static load (id: number): Bluebird<MActor> {
256 return ActorModel.unscoped().findByPk(id) 268 return ActorModel.unscoped().findByPk(id)
257 } 269 }
258 270
259 static loadAccountActorByVideoId (videoId: number, transaction: Sequelize.Transaction) { 271 static loadFull (id: number): Bluebird<MActorFull> {
272 return ActorModel.scope(ScopeNames.FULL).findByPk(id)
273 }
274
275 static loadFromAccountByVideoId (videoId: number, transaction: Sequelize.Transaction): Bluebird<MActor> {
260 const query = { 276 const query = {
261 include: [ 277 include: [
262 { 278 {
@@ -300,7 +316,7 @@ export class ActorModel extends Model<ActorModel> {
300 .then(a => !!a) 316 .then(a => !!a)
301 } 317 }
302 318
303 static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { 319 static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction): Bluebird<MActorFull[]> {
304 const query = { 320 const query = {
305 where: { 321 where: {
306 followersUrl: { 322 followersUrl: {
@@ -313,7 +329,7 @@ export class ActorModel extends Model<ActorModel> {
313 return ActorModel.scope(ScopeNames.FULL).findAll(query) 329 return ActorModel.scope(ScopeNames.FULL).findAll(query)
314 } 330 }
315 331
316 static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction) { 332 static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction): Bluebird<MActorFull> {
317 const query = { 333 const query = {
318 where: { 334 where: {
319 preferredUsername, 335 preferredUsername,
@@ -325,7 +341,7 @@ export class ActorModel extends Model<ActorModel> {
325 return ActorModel.scope(ScopeNames.FULL).findOne(query) 341 return ActorModel.scope(ScopeNames.FULL).findOne(query)
326 } 342 }
327 343
328 static loadByNameAndHost (preferredUsername: string, host: string) { 344 static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> {
329 const query = { 345 const query = {
330 where: { 346 where: {
331 preferredUsername 347 preferredUsername
@@ -344,7 +360,7 @@ export class ActorModel extends Model<ActorModel> {
344 return ActorModel.scope(ScopeNames.FULL).findOne(query) 360 return ActorModel.scope(ScopeNames.FULL).findOne(query)
345 } 361 }
346 362
347 static loadByUrl (url: string, transaction?: Sequelize.Transaction) { 363 static loadByUrl (url: string, transaction?: Sequelize.Transaction): Bluebird<MActorAccountChannelId> {
348 const query = { 364 const query = {
349 where: { 365 where: {
350 url 366 url
@@ -367,7 +383,7 @@ export class ActorModel extends Model<ActorModel> {
367 return ActorModel.unscoped().findOne(query) 383 return ActorModel.unscoped().findOne(query)
368 } 384 }
369 385
370 static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction) { 386 static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction): Bluebird<MActorFull> {
371 const query = { 387 const query = {
372 where: { 388 where: {
373 url 389 url
@@ -387,27 +403,34 @@ export class ActorModel extends Model<ActorModel> {
387 }) 403 })
388 } 404 }
389 405
390 toFormattedJSON () { 406 toFormattedSummaryJSON (this: MActorSummaryFormattable) {
391 let avatar: Avatar = null 407 let avatar: Avatar = null
392 if (this.Avatar) { 408 if (this.Avatar) {
393 avatar = this.Avatar.toFormattedJSON() 409 avatar = this.Avatar.toFormattedJSON()
394 } 410 }
395 411
396 return { 412 return {
397 id: this.id,
398 url: this.url, 413 url: this.url,
399 name: this.preferredUsername, 414 name: this.preferredUsername,
400 host: this.getHost(), 415 host: this.getHost(),
416 avatar
417 }
418 }
419
420 toFormattedJSON (this: MActorFormattable) {
421 const base = this.toFormattedSummaryJSON()
422
423 return Object.assign(base, {
424 id: this.id,
401 hostRedundancyAllowed: this.getRedundancyAllowed(), 425 hostRedundancyAllowed: this.getRedundancyAllowed(),
402 followingCount: this.followingCount, 426 followingCount: this.followingCount,
403 followersCount: this.followersCount, 427 followersCount: this.followersCount,
404 avatar,
405 createdAt: this.createdAt, 428 createdAt: this.createdAt,
406 updatedAt: this.updatedAt 429 updatedAt: this.updatedAt
407 } 430 })
408 } 431 }
409 432
410 toActivityPubObject (name: string, type: 'Account' | 'Application' | 'VideoChannel') { 433 toActivityPubObject (this: MActorAP, name: string, type: 'Account' | 'Application' | 'VideoChannel') {
411 let activityPubType 434 let activityPubType
412 if (type === 'Account') { 435 if (type === 'Account') {
413 activityPubType = 'Person' as 'Person' 436 activityPubType = 'Person' as 'Person'
@@ -494,7 +517,7 @@ export class ActorModel extends Model<ActorModel> {
494 return this.serverId === null 517 return this.serverId === null
495 } 518 }
496 519
497 getWebfingerUrl () { 520 getWebfingerUrl (this: MActorServer) {
498 return 'acct:' + this.preferredUsername + '@' + this.getHost() 521 return 'acct:' + this.preferredUsername + '@' + this.getHost()
499 } 522 }
500 523
@@ -502,7 +525,7 @@ export class ActorModel extends Model<ActorModel> {
502 return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername 525 return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername
503 } 526 }
504 527
505 getHost () { 528 getHost (this: MActorHost) {
506 return this.Server ? this.Server.host : WEBSERVER.HOST 529 return this.Server ? this.Server.host : WEBSERVER.HOST
507 } 530 }
508 531
diff --git a/server/models/avatar/avatar.ts b/server/models/avatar/avatar.ts
index b40144592..950e4b181 100644
--- a/server/models/avatar/avatar.ts
+++ b/server/models/avatar/avatar.ts
@@ -7,6 +7,7 @@ import { remove } from 'fs-extra'
7import { CONFIG } from '../../initializers/config' 7import { CONFIG } from '../../initializers/config'
8import { throwIfNotValid } from '../utils' 8import { throwIfNotValid } from '../utils'
9import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 9import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
10import { MAvatarFormattable } from '@server/typings/models'
10 11
11@Table({ 12@Table({
12 tableName: 'avatar', 13 tableName: 'avatar',
@@ -57,7 +58,7 @@ export class AvatarModel extends Model<AvatarModel> {
57 return AvatarModel.findOne(query) 58 return AvatarModel.findOne(query)
58 } 59 }
59 60
60 toFormattedJSON (): Avatar { 61 toFormattedJSON (this: MAvatarFormattable): Avatar {
61 return { 62 return {
62 path: this.getStaticPath(), 63 path: this.getStaticPath(),
63 createdAt: this.createdAt, 64 createdAt: this.createdAt,
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts
index 903d551df..b680be237 100644
--- a/server/models/oauth/oauth-token.ts
+++ b/server/models/oauth/oauth-token.ts
@@ -18,6 +18,8 @@ import { Transaction } from 'sequelize'
18import { AccountModel } from '../account/account' 18import { AccountModel } from '../account/account'
19import { ActorModel } from '../activitypub/actor' 19import { ActorModel } from '../activitypub/actor'
20import { clearCacheByToken } from '../../lib/oauth-model' 20import { clearCacheByToken } from '../../lib/oauth-model'
21import * as Bluebird from 'bluebird'
22import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
21 23
22export type OAuthTokenInfo = { 24export type OAuthTokenInfo = {
23 refreshToken: string 25 refreshToken: string
@@ -160,7 +162,7 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
160 }) 162 })
161 } 163 }
162 164
163 static getByTokenAndPopulateUser (bearerToken: string) { 165 static getByTokenAndPopulateUser (bearerToken: string): Bluebird<MOAuthTokenUser> {
164 const query = { 166 const query = {
165 where: { 167 where: {
166 accessToken: bearerToken 168 accessToken: bearerToken
@@ -170,13 +172,13 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
170 return OAuthTokenModel.scope(ScopeNames.WITH_USER) 172 return OAuthTokenModel.scope(ScopeNames.WITH_USER)
171 .findOne(query) 173 .findOne(query)
172 .then(token => { 174 .then(token => {
173 if (token) token[ 'user' ] = token.User 175 if (!token) return null
174 176
175 return token 177 return Object.assign(token, { user: token.User })
176 }) 178 })
177 } 179 }
178 180
179 static getByRefreshTokenAndPopulateUser (refreshToken: string) { 181 static getByRefreshTokenAndPopulateUser (refreshToken: string): Bluebird<MOAuthTokenUser> {
180 const query = { 182 const query = {
181 where: { 183 where: {
182 refreshToken: refreshToken 184 refreshToken: refreshToken
@@ -186,12 +188,9 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
186 return OAuthTokenModel.scope(ScopeNames.WITH_USER) 188 return OAuthTokenModel.scope(ScopeNames.WITH_USER)
187 .findOne(query) 189 .findOne(query)
188 .then(token => { 190 .then(token => {
189 if (token) { 191 if (!token) return new OAuthTokenModel()
190 token['user'] = token.User 192
191 return token 193 return Object.assign(token, { user: token.User })
192 } else {
193 return new OAuthTokenModel()
194 }
195 }) 194 })
196 } 195 }
197 196
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts
index 3df1c4f9c..61d9a5612 100644
--- a/server/models/redundancy/video-redundancy.ts
+++ b/server/models/redundancy/video-redundancy.ts
@@ -30,6 +30,7 @@ import * as Bluebird from 'bluebird'
30import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' 30import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize'
31import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' 31import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist'
32import { CONFIG } from '../../initializers/config' 32import { CONFIG } from '../../initializers/config'
33import { MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models'
33 34
34export enum ScopeNames { 35export enum ScopeNames {
35 WITH_VIDEO = 'WITH_VIDEO' 36 WITH_VIDEO = 'WITH_VIDEO'
@@ -166,7 +167,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
166 return undefined 167 return undefined
167 } 168 }
168 169
169 static async loadLocalByFileId (videoFileId: number) { 170 static async loadLocalByFileId (videoFileId: number): Promise<MVideoRedundancyVideo> {
170 const actor = await getServerActor() 171 const actor = await getServerActor()
171 172
172 const query = { 173 const query = {
@@ -179,7 +180,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
179 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) 180 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
180 } 181 }
181 182
182 static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number) { 183 static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number): Promise<MVideoRedundancyVideo> {
183 const actor = await getServerActor() 184 const actor = await getServerActor()
184 185
185 const query = { 186 const query = {
@@ -192,7 +193,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
192 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) 193 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
193 } 194 }
194 195
195 static loadByUrl (url: string, transaction?: Transaction) { 196 static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> {
196 const query = { 197 const query = {
197 where: { 198 where: {
198 url 199 url
@@ -306,7 +307,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
306 return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query)) 307 return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query))
307 } 308 }
308 309
309 static async loadOldestLocalThatAlreadyExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number) { 310 static async loadOldestLocalExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number): Promise<MVideoRedundancyVideo> {
310 const expiredDate = new Date() 311 const expiredDate = new Date()
311 expiredDate.setMilliseconds(expiredDate.getMilliseconds() - expiresAfterMs) 312 expiredDate.setMilliseconds(expiredDate.getMilliseconds() - expiresAfterMs)
312 313
@@ -487,7 +488,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
487 return !!this.strategy 488 return !!this.strategy
488 } 489 }
489 490
490 toActivityPubObject (): CacheFileObject { 491 toActivityPubObject (this: MVideoRedundancyAP): CacheFileObject {
491 if (this.VideoStreamingPlaylist) { 492 if (this.VideoStreamingPlaylist) {
492 return { 493 return {
493 id: this.url, 494 id: this.url,
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts
index a15f9a7e2..d094da1f5 100644
--- a/server/models/server/plugin.ts
+++ b/server/models/server/plugin.ts
@@ -11,6 +11,8 @@ import { PluginType } from '../../../shared/models/plugins/plugin.type'
11import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model' 11import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model'
12import { FindAndCountOptions, json } from 'sequelize' 12import { FindAndCountOptions, json } from 'sequelize'
13import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' 13import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model'
14import * as Bluebird from 'bluebird'
15import { MPlugin, MPluginFormattable } from '@server/typings/models'
14 16
15@DefaultScope(() => ({ 17@DefaultScope(() => ({
16 attributes: { 18 attributes: {
@@ -85,7 +87,7 @@ export class PluginModel extends Model<PluginModel> {
85 @UpdatedAt 87 @UpdatedAt
86 updatedAt: Date 88 updatedAt: Date
87 89
88 static listEnabledPluginsAndThemes () { 90 static listEnabledPluginsAndThemes (): Bluebird<MPlugin[]> {
89 const query = { 91 const query = {
90 where: { 92 where: {
91 enabled: true, 93 enabled: true,
@@ -96,7 +98,7 @@ export class PluginModel extends Model<PluginModel> {
96 return PluginModel.findAll(query) 98 return PluginModel.findAll(query)
97 } 99 }
98 100
99 static loadByNpmName (npmName: string) { 101 static loadByNpmName (npmName: string): Bluebird<MPlugin> {
100 const name = this.normalizePluginName(npmName) 102 const name = this.normalizePluginName(npmName)
101 const type = this.getTypeFromNpmName(npmName) 103 const type = this.getTypeFromNpmName(npmName)
102 104
@@ -206,13 +208,13 @@ export class PluginModel extends Model<PluginModel> {
206 if (options.pluginType) query.where['type'] = options.pluginType 208 if (options.pluginType) query.where['type'] = options.pluginType
207 209
208 return PluginModel 210 return PluginModel
209 .findAndCountAll(query) 211 .findAndCountAll<MPlugin>(query)
210 .then(({ rows, count }) => { 212 .then(({ rows, count }) => {
211 return { total: count, data: rows } 213 return { total: count, data: rows }
212 }) 214 })
213 } 215 }
214 216
215 static listInstalled () { 217 static listInstalled (): Bluebird<MPlugin[]> {
216 const query = { 218 const query = {
217 where: { 219 where: {
218 uninstalled: false 220 uninstalled: false
@@ -251,7 +253,7 @@ export class PluginModel extends Model<PluginModel> {
251 return result 253 return result
252 } 254 }
253 255
254 toFormattedJSON (): PeerTubePlugin { 256 toFormattedJSON (this: MPluginFormattable): PeerTubePlugin {
255 return { 257 return {
256 name: this.name, 258 name: this.name,
257 type: this.type, 259 type: this.type,
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts
index 5138b0f76..3e9687191 100644
--- a/server/models/server/server-blocklist.ts
+++ b/server/models/server/server-blocklist.ts
@@ -3,6 +3,8 @@ import { AccountModel } from '../account/account'
3import { ServerModel } from './server' 3import { ServerModel } from './server'
4import { ServerBlock } from '../../../shared/models/blocklist' 4import { ServerBlock } from '../../../shared/models/blocklist'
5import { getSort } from '../utils' 5import { getSort } from '../utils'
6import * as Bluebird from 'bluebird'
7import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/typings/models'
6 8
7enum ScopeNames { 9enum ScopeNames {
8 WITH_ACCOUNT = 'WITH_ACCOUNT', 10 WITH_ACCOUNT = 'WITH_ACCOUNT',
@@ -73,7 +75,7 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> {
73 }) 75 })
74 BlockedServer: ServerModel 76 BlockedServer: ServerModel
75 77
76 static loadByAccountAndHost (accountId: number, host: string) { 78 static loadByAccountAndHost (accountId: number, host: string): Bluebird<MServerBlocklist> {
77 const query = { 79 const query = {
78 where: { 80 where: {
79 accountId 81 accountId
@@ -104,13 +106,13 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> {
104 106
105 return ServerBlocklistModel 107 return ServerBlocklistModel
106 .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ]) 108 .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ])
107 .findAndCountAll(query) 109 .findAndCountAll<MServerBlocklistAccountServer>(query)
108 .then(({ rows, count }) => { 110 .then(({ rows, count }) => {
109 return { total: count, data: rows } 111 return { total: count, data: rows }
110 }) 112 })
111 } 113 }
112 114
113 toFormattedJSON (): ServerBlock { 115 toFormattedJSON (this: MServerBlocklistFormattable): ServerBlock {
114 return { 116 return {
115 byAccount: this.ByAccount.toFormattedJSON(), 117 byAccount: this.ByAccount.toFormattedJSON(),
116 blockedServer: this.BlockedServer.toFormattedJSON(), 118 blockedServer: this.BlockedServer.toFormattedJSON(),
diff --git a/server/models/server/server.ts b/server/models/server/server.ts
index 1d211f1e0..3b6759b5c 100644
--- a/server/models/server/server.ts
+++ b/server/models/server/server.ts
@@ -2,8 +2,9 @@ import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, Updat
2import { isHostValid } from '../../helpers/custom-validators/servers' 2import { isHostValid } from '../../helpers/custom-validators/servers'
3import { ActorModel } from '../activitypub/actor' 3import { ActorModel } from '../activitypub/actor'
4import { throwIfNotValid } from '../utils' 4import { throwIfNotValid } from '../utils'
5import { AccountBlocklistModel } from '../account/account-blocklist'
6import { ServerBlocklistModel } from './server-blocklist' 5import { ServerBlocklistModel } from './server-blocklist'
6import * as Bluebird from 'bluebird'
7import { MServer, MServerFormattable } from '@server/typings/models/server'
7 8
8@Table({ 9@Table({
9 tableName: 'server', 10 tableName: 'server',
@@ -50,7 +51,7 @@ export class ServerModel extends Model<ServerModel> {
50 }) 51 })
51 BlockedByAccounts: ServerBlocklistModel[] 52 BlockedByAccounts: ServerBlocklistModel[]
52 53
53 static loadByHost (host: string) { 54 static loadByHost (host: string): Bluebird<MServer> {
54 const query = { 55 const query = {
55 where: { 56 where: {
56 host 57 host
@@ -64,7 +65,7 @@ export class ServerModel extends Model<ServerModel> {
64 return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0 65 return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0
65 } 66 }
66 67
67 toFormattedJSON () { 68 toFormattedJSON (this: MServerFormattable) {
68 return { 69 return {
69 host: this.host 70 host: this.host
70 } 71 }
diff --git a/server/models/video/schedule-video-update.ts b/server/models/video/schedule-video-update.ts
index 603d55692..fc2a424aa 100644
--- a/server/models/video/schedule-video-update.ts
+++ b/server/models/video/schedule-video-update.ts
@@ -2,6 +2,7 @@ import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Ta
2import { ScopeNames as VideoScopeNames, VideoModel } from './video' 2import { ScopeNames as VideoScopeNames, VideoModel } from './video'
3import { VideoPrivacy } from '../../../shared/models/videos' 3import { VideoPrivacy } from '../../../shared/models/videos'
4import { Op, Transaction } from 'sequelize' 4import { Op, Transaction } from 'sequelize'
5import { MScheduleVideoUpdateFormattable } from '@server/typings/models'
5 6
6@Table({ 7@Table({
7 tableName: 'scheduleVideoUpdate', 8 tableName: 'scheduleVideoUpdate',
@@ -96,7 +97,7 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> {
96 return ScheduleVideoUpdateModel.destroy(query) 97 return ScheduleVideoUpdateModel.destroy(query)
97 } 98 }
98 99
99 toFormattedJSON () { 100 toFormattedJSON (this: MScheduleVideoUpdateFormattable) {
100 return { 101 return {
101 updateAt: this.updateAt, 102 updateAt: this.updateAt,
102 privacy: this.privacy || undefined 103 privacy: this.privacy || undefined
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts
index 0fc3cfd4c..ed8df8b48 100644
--- a/server/models/video/tag.ts
+++ b/server/models/video/tag.ts
@@ -1,11 +1,12 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { QueryTypes, Transaction } from 'sequelize' 2import { fn, QueryTypes, Transaction, col } from 'sequelize'
3import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 3import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
4import { isVideoTagValid } from '../../helpers/custom-validators/videos' 4import { isVideoTagValid } from '../../helpers/custom-validators/videos'
5import { throwIfNotValid } from '../utils' 5import { throwIfNotValid } from '../utils'
6import { VideoModel } from './video' 6import { VideoModel } from './video'
7import { VideoTagModel } from './video-tag' 7import { VideoTagModel } from './video-tag'
8import { VideoPrivacy, VideoState } from '../../../shared/models/videos' 8import { VideoPrivacy, VideoState } from '../../../shared/models/videos'
9import { MTag } from '@server/typings/models'
9 10
10@Table({ 11@Table({
11 tableName: 'tag', 12 tableName: 'tag',
@@ -14,6 +15,10 @@ import { VideoPrivacy, VideoState } from '../../../shared/models/videos'
14 { 15 {
15 fields: [ 'name' ], 16 fields: [ 'name' ],
16 unique: true 17 unique: true
18 },
19 {
20 name: 'tag_lower_name',
21 fields: [ fn('lower', col('name')) ] as any // FIXME: typings
17 } 22 }
18 ] 23 ]
19}) 24})
@@ -37,10 +42,10 @@ export class TagModel extends Model<TagModel> {
37 }) 42 })
38 Videos: VideoModel[] 43 Videos: VideoModel[]
39 44
40 static findOrCreateTags (tags: string[], transaction: Transaction) { 45 static findOrCreateTags (tags: string[], transaction: Transaction): Promise<MTag[]> {
41 if (tags === null) return [] 46 if (tags === null) return Promise.resolve([])
42 47
43 const tasks: Bluebird<TagModel>[] = [] 48 const tasks: Bluebird<MTag>[] = []
44 tags.forEach(tag => { 49 tags.forEach(tag => {
45 const query = { 50 const query = {
46 where: { 51 where: {
@@ -52,7 +57,7 @@ export class TagModel extends Model<TagModel> {
52 transaction 57 transaction
53 } 58 }
54 59
55 const promise = TagModel.findOrCreate(query) 60 const promise = TagModel.findOrCreate<MTag>(query)
56 .then(([ tagInstance ]) => tagInstance) 61 .then(([ tagInstance ]) => tagInstance)
57 tasks.push(promise) 62 tasks.push(promise)
58 }) 63 })
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts
index 1ac7919b3..3636db18d 100644
--- a/server/models/video/video-abuse.ts
+++ b/server/models/video/video-abuse.ts
@@ -7,10 +7,13 @@ import {
7 isVideoAbuseStateValid 7 isVideoAbuseStateValid
8} from '../../helpers/custom-validators/video-abuses' 8} from '../../helpers/custom-validators/video-abuses'
9import { AccountModel } from '../account/account' 9import { AccountModel } from '../account/account'
10import { getSort, throwIfNotValid } from '../utils' 10import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils'
11import { VideoModel } from './video' 11import { VideoModel } from './video'
12import { VideoAbuseState } from '../../../shared' 12import { VideoAbuseState } from '../../../shared'
13import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' 13import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants'
14import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models'
15import * as Bluebird from 'bluebird'
16import { literal, Op } from 'sequelize'
14 17
15@Table({ 18@Table({
16 tableName: 'videoAbuse', 19 tableName: 'videoAbuse',
@@ -73,7 +76,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
73 }) 76 })
74 Video: VideoModel 77 Video: VideoModel
75 78
76 static loadByIdAndVideoId (id: number, videoId: number) { 79 static loadByIdAndVideoId (id: number, videoId: number): Bluebird<MVideoAbuse> {
77 const query = { 80 const query = {
78 where: { 81 where: {
79 id, 82 id,
@@ -83,11 +86,25 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
83 return VideoAbuseModel.findOne(query) 86 return VideoAbuseModel.findOne(query)
84 } 87 }
85 88
86 static listForApi (start: number, count: number, sort: string) { 89 static listForApi (parameters: {
90 start: number,
91 count: number,
92 sort: string,
93 serverAccountId: number
94 user?: MUserAccountId
95 }) {
96 const { start, count, sort, user, serverAccountId } = parameters
97 const userAccountId = user ? user.Account.id : undefined
98
87 const query = { 99 const query = {
88 offset: start, 100 offset: start,
89 limit: count, 101 limit: count,
90 order: getSort(sort), 102 order: getSort(sort),
103 where: {
104 reporterAccountId: {
105 [Op.notIn]: literal('(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')')
106 }
107 },
91 include: [ 108 include: [
92 { 109 {
93 model: AccountModel, 110 model: AccountModel,
@@ -106,7 +123,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
106 }) 123 })
107 } 124 }
108 125
109 toFormattedJSON (): VideoAbuse { 126 toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse {
110 return { 127 return {
111 id: this.id, 128 id: this.id,
112 reason: this.reason, 129 reason: this.reason,
@@ -125,7 +142,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
125 } 142 }
126 } 143 }
127 144
128 toActivityPubObject (): VideoAbuseObject { 145 toActivityPubObject (this: MVideoAbuseVideo): VideoAbuseObject {
129 return { 146 return {
130 type: 'Flag' as 'Flag', 147 type: 'Flag' as 'Flag',
131 content: this.reason, 148 content: this.reason,
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts
index 22d949da0..b4df6cd6a 100644
--- a/server/models/video/video-blacklist.ts
+++ b/server/models/video/video-blacklist.ts
@@ -1,12 +1,14 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { getSortOnModel, SortType, throwIfNotValid } from '../utils' 2import { getSortOnModel, SortType, throwIfNotValid } from '../utils'
3import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' 3import { VideoModel } from './video'
4import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' 4import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
5import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' 5import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist'
6import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' 6import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos'
7import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 7import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
8import { FindOptions } from 'sequelize' 8import { FindOptions } from 'sequelize'
9import { ThumbnailModel } from './thumbnail' 9import { ThumbnailModel } from './thumbnail'
10import * as Bluebird from 'bluebird'
11import { MVideoBlacklist, MVideoBlacklistFormattable } from '@server/typings/models'
10 12
11@Table({ 13@Table({
12 tableName: 'videoBlacklist', 14 tableName: 'videoBlacklist',
@@ -99,7 +101,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
99 }) 101 })
100 } 102 }
101 103
102 static loadByVideoId (id: number) { 104 static loadByVideoId (id: number): Bluebird<MVideoBlacklist> {
103 const query = { 105 const query = {
104 where: { 106 where: {
105 videoId: id 107 videoId: id
@@ -109,7 +111,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
109 return VideoBlacklistModel.findOne(query) 111 return VideoBlacklistModel.findOne(query)
110 } 112 }
111 113
112 toFormattedJSON (): VideoBlacklist { 114 toFormattedJSON (this: MVideoBlacklistFormattable): VideoBlacklist {
113 return { 115 return {
114 id: this.id, 116 id: this.id,
115 createdAt: this.createdAt, 117 createdAt: this.createdAt,
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts
index a01565851..ad5801768 100644
--- a/server/models/video/video-caption.ts
+++ b/server/models/video/video-caption.ts
@@ -21,6 +21,8 @@ import { join } from 'path'
21import { logger } from '../../helpers/logger' 21import { logger } from '../../helpers/logger'
22import { remove } from 'fs-extra' 22import { remove } from 'fs-extra'
23import { CONFIG } from '../../initializers/config' 23import { CONFIG } from '../../initializers/config'
24import * as Bluebird from 'bluebird'
25import { MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models'
24 26
25export enum ScopeNames { 27export enum ScopeNames {
26 WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' 28 WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
@@ -30,7 +32,7 @@ export enum ScopeNames {
30 [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { 32 [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: {
31 include: [ 33 include: [
32 { 34 {
33 attributes: [ 'uuid', 'remote' ], 35 attributes: [ 'id', 'uuid', 'remote' ],
34 model: VideoModel.unscoped(), 36 model: VideoModel.unscoped(),
35 required: true 37 required: true
36 } 38 }
@@ -93,7 +95,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
93 return undefined 95 return undefined
94 } 96 }
95 97
96 static loadByVideoIdAndLanguage (videoId: string | number, language: string) { 98 static loadByVideoIdAndLanguage (videoId: string | number, language: string): Bluebird<MVideoCaptionVideo> {
97 const videoInclude = { 99 const videoInclude = {
98 model: VideoModel.unscoped(), 100 model: VideoModel.unscoped(),
99 attributes: [ 'id', 'remote', 'uuid' ], 101 attributes: [ 'id', 'remote', 'uuid' ],
@@ -122,7 +124,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
122 .then(([ caption ]) => caption) 124 .then(([ caption ]) => caption)
123 } 125 }
124 126
125 static listVideoCaptions (videoId: number) { 127 static listVideoCaptions (videoId: number): Bluebird<MVideoCaptionVideo[]> {
126 const query = { 128 const query = {
127 order: [ [ 'language', 'ASC' ] ] as OrderItem[], 129 order: [ [ 'language', 'ASC' ] ] as OrderItem[],
128 where: { 130 where: {
@@ -152,7 +154,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
152 return this.Video.remote === false 154 return this.Video.remote === false
153 } 155 }
154 156
155 toFormattedJSON (): VideoCaption { 157 toFormattedJSON (this: MVideoCaptionFormattable): VideoCaption {
156 return { 158 return {
157 language: { 159 language: {
158 id: this.language, 160 id: this.language,
@@ -162,15 +164,15 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
162 } 164 }
163 } 165 }
164 166
165 getCaptionStaticPath () { 167 getCaptionStaticPath (this: MVideoCaptionFormattable) {
166 return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName()) 168 return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName())
167 } 169 }
168 170
169 getCaptionName () { 171 getCaptionName (this: MVideoCaptionFormattable) {
170 return `${this.Video.uuid}-${this.language}.vtt` 172 return `${this.Video.uuid}-${this.language}.vtt`
171 } 173 }
172 174
173 removeCaptionFile () { 175 removeCaptionFile (this: MVideoCaptionFormattable) {
174 return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) 176 return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName())
175 } 177 }
176} 178}
diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts
index b545a2f8c..f7a351329 100644
--- a/server/models/video/video-change-ownership.ts
+++ b/server/models/video/video-change-ownership.ts
@@ -3,6 +3,8 @@ import { AccountModel } from '../account/account'
3import { ScopeNames as VideoScopeNames, VideoModel } from './video' 3import { ScopeNames as VideoScopeNames, VideoModel } from './video'
4import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' 4import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos'
5import { getSort } from '../utils' 5import { getSort } from '../utils'
6import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership'
7import * as Bluebird from 'bluebird'
6 8
7enum ScopeNames { 9enum ScopeNames {
8 WITH_ACCOUNTS = 'WITH_ACCOUNTS', 10 WITH_ACCOUNTS = 'WITH_ACCOUNTS',
@@ -108,16 +110,16 @@ export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel>
108 110
109 return Promise.all([ 111 return Promise.all([
110 VideoChangeOwnershipModel.scope(ScopeNames.WITH_ACCOUNTS).count(query), 112 VideoChangeOwnershipModel.scope(ScopeNames.WITH_ACCOUNTS).count(query),
111 VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]).findAll(query) 113 VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]).findAll<MVideoChangeOwnershipFull>(query)
112 ]).then(([ count, rows ]) => ({ total: count, data: rows })) 114 ]).then(([ count, rows ]) => ({ total: count, data: rows }))
113 } 115 }
114 116
115 static load (id: number) { 117 static load (id: number): Bluebird<MVideoChangeOwnershipFull> {
116 return VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]) 118 return VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ])
117 .findByPk(id) 119 .findByPk(id)
118 } 120 }
119 121
120 toFormattedJSON (): VideoChangeOwnership { 122 toFormattedJSON (this: MVideoChangeOwnershipFormattable): VideoChangeOwnership {
121 return { 123 return {
122 id: this.id, 124 id: this.id,
123 status: this.status, 125 status: this.status,
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index 6241a75a3..7178631b4 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -33,6 +33,15 @@ import { ServerModel } from '../server/server'
33import { FindOptions, ModelIndexesOptions, Op } from 'sequelize' 33import { FindOptions, ModelIndexesOptions, Op } from 'sequelize'
34import { AvatarModel } from '../avatar/avatar' 34import { AvatarModel } from '../avatar/avatar'
35import { VideoPlaylistModel } from './video-playlist' 35import { VideoPlaylistModel } from './video-playlist'
36import * as Bluebird from 'bluebird'
37import {
38 MChannelAccountDefault,
39 MChannelActor,
40 MChannelActorAccountDefaultVideos,
41 MChannelAP,
42 MChannelFormattable,
43 MChannelSummaryFormattable
44} from '../../typings/models/video'
36 45
37// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation 46// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
38const indexes: ModelIndexesOptions[] = [ 47const indexes: ModelIndexesOptions[] = [
@@ -47,7 +56,7 @@ const indexes: ModelIndexesOptions[] = [
47] 56]
48 57
49export enum ScopeNames { 58export enum ScopeNames {
50 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', 59 FOR_API = 'FOR_API',
51 WITH_ACCOUNT = 'WITH_ACCOUNT', 60 WITH_ACCOUNT = 'WITH_ACCOUNT',
52 WITH_ACTOR = 'WITH_ACTOR', 61 WITH_ACTOR = 'WITH_ACTOR',
53 WITH_VIDEOS = 'WITH_VIDEOS', 62 WITH_VIDEOS = 'WITH_VIDEOS',
@@ -74,10 +83,10 @@ export type SummaryOptions = {
74@Scopes(() => ({ 83@Scopes(() => ({
75 [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => { 84 [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => {
76 const base: FindOptions = { 85 const base: FindOptions = {
77 attributes: [ 'name', 'description', 'id', 'actorId' ], 86 attributes: [ 'id', 'name', 'description', 'actorId' ],
78 include: [ 87 include: [
79 { 88 {
80 attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ], 89 attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ],
81 model: ActorModel.unscoped(), 90 model: ActorModel.unscoped(),
82 required: true, 91 required: true,
83 include: [ 92 include: [
@@ -106,7 +115,7 @@ export type SummaryOptions = {
106 115
107 return base 116 return base
108 }, 117 },
109 [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => { 118 [ScopeNames.FOR_API]: (options: AvailableForListOptions) => {
110 // Only list local channels OR channels that are on an instance followed by actorId 119 // Only list local channels OR channels that are on an instance followed by actorId
111 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId) 120 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId)
112 121
@@ -268,7 +277,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
268 } 277 }
269 278
270 const scopes = { 279 const scopes = {
271 method: [ ScopeNames.AVAILABLE_FOR_LIST, { actorId } as AvailableForListOptions ] 280 method: [ ScopeNames.FOR_API, { actorId } as AvailableForListOptions ]
272 } 281 }
273 return VideoChannelModel 282 return VideoChannelModel
274 .scope(scopes) 283 .scope(scopes)
@@ -278,7 +287,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
278 }) 287 })
279 } 288 }
280 289
281 static listLocalsForSitemap (sort: string) { 290 static listLocalsForSitemap (sort: string): Bluebird<MChannelActor[]> {
282 const query = { 291 const query = {
283 attributes: [ ], 292 attributes: [ ],
284 offset: 0, 293 offset: 0,
@@ -331,7 +340,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
331 } 340 }
332 341
333 const scopes = { 342 const scopes = {
334 method: [ ScopeNames.AVAILABLE_FOR_LIST, { actorId: options.actorId } as AvailableForListOptions ] 343 method: [ ScopeNames.FOR_API, { actorId: options.actorId } as AvailableForListOptions ]
335 } 344 }
336 return VideoChannelModel 345 return VideoChannelModel
337 .scope(scopes) 346 .scope(scopes)
@@ -369,13 +378,13 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
369 }) 378 })
370 } 379 }
371 380
372 static loadByIdAndPopulateAccount (id: number) { 381 static loadByIdAndPopulateAccount (id: number): Bluebird<MChannelAccountDefault> {
373 return VideoChannelModel.unscoped() 382 return VideoChannelModel.unscoped()
374 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) 383 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
375 .findByPk(id) 384 .findByPk(id)
376 } 385 }
377 386
378 static loadByIdAndAccount (id: number, accountId: number) { 387 static loadByIdAndAccount (id: number, accountId: number): Bluebird<MChannelAccountDefault> {
379 const query = { 388 const query = {
380 where: { 389 where: {
381 id, 390 id,
@@ -388,13 +397,13 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
388 .findOne(query) 397 .findOne(query)
389 } 398 }
390 399
391 static loadAndPopulateAccount (id: number) { 400 static loadAndPopulateAccount (id: number): Bluebird<MChannelAccountDefault> {
392 return VideoChannelModel.unscoped() 401 return VideoChannelModel.unscoped()
393 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) 402 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
394 .findByPk(id) 403 .findByPk(id)
395 } 404 }
396 405
397 static loadByUrlAndPopulateAccount (url: string) { 406 static loadByUrlAndPopulateAccount (url: string): Bluebird<MChannelAccountDefault> {
398 const query = { 407 const query = {
399 include: [ 408 include: [
400 { 409 {
@@ -420,7 +429,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
420 return VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host) 429 return VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host)
421 } 430 }
422 431
423 static loadLocalByNameAndPopulateAccount (name: string) { 432 static loadLocalByNameAndPopulateAccount (name: string): Bluebird<MChannelAccountDefault> {
424 const query = { 433 const query = {
425 include: [ 434 include: [
426 { 435 {
@@ -439,7 +448,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
439 .findOne(query) 448 .findOne(query)
440 } 449 }
441 450
442 static loadByNameAndHostAndPopulateAccount (name: string, host: string) { 451 static loadByNameAndHostAndPopulateAccount (name: string, host: string): Bluebird<MChannelAccountDefault> {
443 const query = { 452 const query = {
444 include: [ 453 include: [
445 { 454 {
@@ -464,7 +473,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
464 .findOne(query) 473 .findOne(query)
465 } 474 }
466 475
467 static loadAndPopulateAccountAndVideos (id: number) { 476 static loadAndPopulateAccountAndVideos (id: number): Bluebird<MChannelActorAccountDefaultVideos> {
468 const options = { 477 const options = {
469 include: [ 478 include: [
470 VideoModel 479 VideoModel
@@ -476,7 +485,20 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
476 .findByPk(id, options) 485 .findByPk(id, options)
477 } 486 }
478 487
479 toFormattedJSON (): VideoChannel { 488 toFormattedSummaryJSON (this: MChannelSummaryFormattable): VideoChannelSummary {
489 const actor = this.Actor.toFormattedSummaryJSON()
490
491 return {
492 id: this.id,
493 name: actor.name,
494 displayName: this.getDisplayName(),
495 url: actor.url,
496 host: actor.host,
497 avatar: actor.avatar
498 }
499 }
500
501 toFormattedJSON (this: MChannelFormattable): VideoChannel {
480 const actor = this.Actor.toFormattedJSON() 502 const actor = this.Actor.toFormattedJSON()
481 const videoChannel = { 503 const videoChannel = {
482 id: this.id, 504 id: this.id,
@@ -494,20 +516,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
494 return Object.assign(actor, videoChannel) 516 return Object.assign(actor, videoChannel)
495 } 517 }
496 518
497 toFormattedSummaryJSON (): VideoChannelSummary { 519 toActivityPubObject (this: MChannelAP): ActivityPubActor {
498 const actor = this.Actor.toFormattedJSON()
499
500 return {
501 id: this.id,
502 name: actor.name,
503 displayName: this.getDisplayName(),
504 url: actor.url,
505 host: actor.host,
506 avatar: actor.avatar
507 }
508 }
509
510 toActivityPubObject (): ActivityPubActor {
511 const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel') 520 const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel')
512 521
513 return Object.assign(obj, { 522 return Object.assign(obj, {
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts
index 58b75510d..2e4220434 100644
--- a/server/models/video/video-comment.ts
+++ b/server/models/video/video-comment.ts
@@ -1,36 +1,32 @@
1import { 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
2 AllowNull,
3 BeforeDestroy,
4 BelongsTo,
5 Column,
6 CreatedAt,
7 DataType,
8 ForeignKey,
9 Is,
10 Model,
11 Scopes,
12 Table,
13 UpdatedAt
14} from 'sequelize-typescript'
15import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects' 2import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects'
16import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' 3import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object'
17import { VideoComment } from '../../../shared/models/videos/video-comment.model' 4import { VideoComment } from '../../../shared/models/videos/video-comment.model'
18import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 5import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
19import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' 6import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants'
20import { sendDeleteVideoComment } from '../../lib/activitypub/send'
21import { AccountModel } from '../account/account' 7import { AccountModel } from '../account/account'
22import { ActorModel } from '../activitypub/actor' 8import { ActorModel } from '../activitypub/actor'
23import { AvatarModel } from '../avatar/avatar'
24import { ServerModel } from '../server/server'
25import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' 9import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils'
26import { VideoModel } from './video' 10import { VideoModel } from './video'
27import { VideoChannelModel } from './video-channel' 11import { VideoChannelModel } from './video-channel'
28import { getServerActor } from '../../helpers/utils' 12import { getServerActor } from '../../helpers/utils'
29import { UserModel } from '../account/user'
30import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' 13import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
31import { regexpCapture } from '../../helpers/regexp' 14import { regexpCapture } from '../../helpers/regexp'
32import { uniq } from 'lodash' 15import { uniq } from 'lodash'
33import { FindOptions, literal, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' 16import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize'
17import * as Bluebird from 'bluebird'
18import {
19 MComment,
20 MCommentAP,
21 MCommentFormattable,
22 MCommentId,
23 MCommentOwner,
24 MCommentOwnerReplyVideoLight,
25 MCommentOwnerVideo,
26 MCommentOwnerVideoFeed,
27 MCommentOwnerVideoReply
28} from '../../typings/models/video'
29import { MUserAccountId } from '@server/typings/models'
34 30
35enum ScopeNames { 31enum ScopeNames {
36 WITH_ACCOUNT = 'WITH_ACCOUNT', 32 WITH_ACCOUNT = 'WITH_ACCOUNT',
@@ -68,22 +64,7 @@ enum ScopeNames {
68 [ScopeNames.WITH_ACCOUNT]: { 64 [ScopeNames.WITH_ACCOUNT]: {
69 include: [ 65 include: [
70 { 66 {
71 model: AccountModel, 67 model: AccountModel
72 include: [
73 {
74 model: ActorModel,
75 include: [
76 {
77 model: ServerModel,
78 required: false
79 },
80 {
81 model: AvatarModel,
82 required: false
83 }
84 ]
85 }
86 ]
87 } 68 }
88 ] 69 ]
89 }, 70 },
@@ -102,22 +83,12 @@ enum ScopeNames {
102 required: true, 83 required: true,
103 include: [ 84 include: [
104 { 85 {
105 model: VideoChannelModel.unscoped(), 86 model: VideoChannelModel,
106 required: true, 87 required: true,
107 include: [ 88 include: [
108 { 89 {
109 model: ActorModel,
110 required: true
111 },
112 {
113 model: AccountModel, 90 model: AccountModel,
114 required: true, 91 required: true
115 include: [
116 {
117 model: ActorModel,
118 required: true
119 }
120 ]
121 } 92 }
122 ] 93 ]
123 } 94 }
@@ -212,7 +183,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
212 }) 183 })
213 Account: AccountModel 184 Account: AccountModel
214 185
215 static loadById (id: number, t?: Transaction) { 186 static loadById (id: number, t?: Transaction): Bluebird<MComment> {
216 const query: FindOptions = { 187 const query: FindOptions = {
217 where: { 188 where: {
218 id 189 id
@@ -224,7 +195,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
224 return VideoCommentModel.findOne(query) 195 return VideoCommentModel.findOne(query)
225 } 196 }
226 197
227 static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction) { 198 static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction): Bluebird<MCommentOwnerVideoReply> {
228 const query: FindOptions = { 199 const query: FindOptions = {
229 where: { 200 where: {
230 id 201 id
@@ -238,7 +209,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
238 .findOne(query) 209 .findOne(query)
239 } 210 }
240 211
241 static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction) { 212 static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction): Bluebird<MCommentOwnerVideo> {
242 const query: FindOptions = { 213 const query: FindOptions = {
243 where: { 214 where: {
244 url 215 url
@@ -250,7 +221,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
250 return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEO ]).findOne(query) 221 return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEO ]).findOne(query)
251 } 222 }
252 223
253 static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction) { 224 static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction): Bluebird<MCommentOwnerReplyVideoLight> {
254 const query: FindOptions = { 225 const query: FindOptions = {
255 where: { 226 where: {
256 url 227 url
@@ -273,7 +244,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
273 start: number, 244 start: number,
274 count: number, 245 count: number,
275 sort: string, 246 sort: string,
276 user?: UserModel 247 user?: MUserAccountId
277 }) { 248 }) {
278 const { videoId, start, count, sort, user } = parameters 249 const { videoId, start, count, sort, user } = parameters
279 250
@@ -314,7 +285,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
314 static async listThreadCommentsForApi (parameters: { 285 static async listThreadCommentsForApi (parameters: {
315 videoId: number, 286 videoId: number,
316 threadId: number, 287 threadId: number,
317 user?: UserModel 288 user?: MUserAccountId
318 }) { 289 }) {
319 const { videoId, threadId, user } = parameters 290 const { videoId, threadId, user } = parameters
320 291
@@ -353,7 +324,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
353 }) 324 })
354 } 325 }
355 326
356 static listThreadParentComments (comment: VideoCommentModel, t: Transaction, order: 'ASC' | 'DESC' = 'ASC') { 327 static listThreadParentComments (comment: MCommentId, t: Transaction, order: 'ASC' | 'DESC' = 'ASC'): Bluebird<MCommentOwner[]> {
357 const query = { 328 const query = {
358 order: [ [ 'createdAt', order ] ] as Order, 329 order: [ [ 'createdAt', order ] ] as Order,
359 where: { 330 where: {
@@ -389,10 +360,10 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
389 transaction: t 360 transaction: t
390 } 361 }
391 362
392 return VideoCommentModel.findAndCountAll(query) 363 return VideoCommentModel.findAndCountAll<MComment>(query)
393 } 364 }
394 365
395 static listForFeed (start: number, count: number, videoId?: number) { 366 static listForFeed (start: number, count: number, videoId?: number): Bluebird<MCommentOwnerVideoFeed[]> {
396 const query = { 367 const query = {
397 order: [ [ 'createdAt', 'DESC' ] ] as Order, 368 order: [ [ 'createdAt', 'DESC' ] ] as Order,
398 offset: start, 369 offset: start,
@@ -506,7 +477,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
506 return uniq(result) 477 return uniq(result)
507 } 478 }
508 479
509 toFormattedJSON () { 480 toFormattedJSON (this: MCommentFormattable) {
510 return { 481 return {
511 id: this.id, 482 id: this.id,
512 url: this.url, 483 url: this.url,
@@ -521,7 +492,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
521 } as VideoComment 492 } as VideoComment
522 } 493 }
523 494
524 toActivityPubObject (threadParentComments: VideoCommentModel[]): VideoCommentObject { 495 toActivityPubObject (this: MCommentAP, threadParentComments: MCommentOwner[]): VideoCommentObject {
525 let inReplyTo: string 496 let inReplyTo: string
526 // New thread, so in AS we reply to the video 497 // New thread, so in AS we reply to the video
527 if (this.inReplyToCommentId === null) { 498 if (this.inReplyToCommentId === null) {
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts
index 05c490759..6304f741c 100644
--- a/server/models/video/video-file.ts
+++ b/server/models/video/video-file.ts
@@ -25,6 +25,7 @@ import { VideoRedundancyModel } from '../redundancy/video-redundancy'
25import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 25import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
26import { FindOptions, QueryTypes, Transaction } from 'sequelize' 26import { FindOptions, QueryTypes, Transaction } from 'sequelize'
27import { MIMETYPES } from '../../initializers/constants' 27import { MIMETYPES } from '../../initializers/constants'
28import { MVideoFile } from '@server/typings/models'
28 29
29@Table({ 30@Table({
30 tableName: 'videoFile', 31 tableName: 'videoFile',
@@ -166,7 +167,7 @@ export class VideoFileModel extends Model<VideoFileModel> {
166 return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname] 167 return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname]
167 } 168 }
168 169
169 hasSameUniqueKeysThan (other: VideoFileModel) { 170 hasSameUniqueKeysThan (other: MVideoFile) {
170 return this.fps === other.fps && 171 return this.fps === other.fps &&
171 this.resolution === other.resolution && 172 this.resolution === other.resolution &&
172 this.videoId === other.videoId 173 this.videoId === other.videoId
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts
index 284539def..2987aa780 100644
--- a/server/models/video/video-format-utils.ts
+++ b/server/models/video/video-format-utils.ts
@@ -1,6 +1,5 @@
1import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' 1import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos'
2import { VideoModel } from './video' 2import { VideoModel } from './video'
3import { VideoFileModel } from './video-file'
4import { 3import {
5 ActivityPlaylistInfohashesObject, 4 ActivityPlaylistInfohashesObject,
6 ActivityPlaylistSegmentHashesObject, 5 ActivityPlaylistSegmentHashesObject,
@@ -17,7 +16,9 @@ import {
17} from '../../lib/activitypub' 16} from '../../lib/activitypub'
18import { isArray } from '../../helpers/custom-validators/misc' 17import { isArray } from '../../helpers/custom-validators/misc'
19import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' 18import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model'
20import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 19import { MStreamingPlaylistRedundanciesOpt, MVideo, MVideoAP, MVideoFormattable, MVideoFormattableDetails } from '../../typings/models'
20import { MStreamingPlaylistRedundancies } from '../../typings/models/video/video-streaming-playlist'
21import { MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file'
21 22
22export type VideoFormattingJSONOptions = { 23export type VideoFormattingJSONOptions = {
23 completeDescription?: boolean 24 completeDescription?: boolean
@@ -28,7 +29,7 @@ export type VideoFormattingJSONOptions = {
28 blacklistInfo?: boolean 29 blacklistInfo?: boolean
29 } 30 }
30} 31}
31function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormattingJSONOptions): Video { 32function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video {
32 const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined 33 const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined
33 34
34 const videoObject: Video = { 35 const videoObject: Video = {
@@ -102,7 +103,7 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting
102 return videoObject 103 return videoObject
103} 104}
104 105
105function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { 106function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): VideoDetails {
106 const formattedJson = video.toFormattedJSON({ 107 const formattedJson = video.toFormattedJSON({
107 additionalAttributes: { 108 additionalAttributes: {
108 scheduledUpdate: true, 109 scheduledUpdate: true,
@@ -114,7 +115,7 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails {
114 115
115 const tags = video.Tags ? video.Tags.map(t => t.name) : [] 116 const tags = video.Tags ? video.Tags.map(t => t.name) : []
116 117
117 const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists) 118 const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video.VideoStreamingPlaylists)
118 119
119 const detailsJson = { 120 const detailsJson = {
120 support: video.support, 121 support: video.support,
@@ -142,7 +143,7 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails {
142 return Object.assign(formattedJson, detailsJson) 143 return Object.assign(formattedJson, detailsJson)
143} 144}
144 145
145function streamingPlaylistsModelToFormattedJSON (video: VideoModel, playlists: VideoStreamingPlaylistModel[]): VideoStreamingPlaylist[] { 146function streamingPlaylistsModelToFormattedJSON (playlists: MStreamingPlaylistRedundanciesOpt[]): VideoStreamingPlaylist[] {
146 if (isArray(playlists) === false) return [] 147 if (isArray(playlists) === false) return []
147 148
148 return playlists 149 return playlists
@@ -161,7 +162,7 @@ function streamingPlaylistsModelToFormattedJSON (video: VideoModel, playlists: V
161 }) 162 })
162} 163}
163 164
164function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFileModel[]): VideoFile[] { 165function videoFilesModelToFormattedJSON (video: MVideo, videoFiles: MVideoFileRedundanciesOpt[]): VideoFile[] {
165 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() 166 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
166 167
167 return videoFiles 168 return videoFiles
@@ -189,7 +190,7 @@ function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFil
189 }) 190 })
190} 191}
191 192
192function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { 193function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject {
193 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() 194 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
194 if (!video.Tags) video.Tags = [] 195 if (!video.Tags) video.Tags = []
195 196
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts
index 480a671c8..af5314ce9 100644
--- a/server/models/video/video-import.ts
+++ b/server/models/video/video-import.ts
@@ -20,6 +20,8 @@ import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../help
20import { VideoImport, VideoImportState } from '../../../shared' 20import { VideoImport, VideoImportState } from '../../../shared'
21import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' 21import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
22import { UserModel } from '../account/user' 22import { UserModel } from '../account/user'
23import * as Bluebird from 'bluebird'
24import { MVideoImportDefault, MVideoImportFormattable } from '@server/typings/models/video/video-import'
23 25
24@DefaultScope(() => ({ 26@DefaultScope(() => ({
25 include: [ 27 include: [
@@ -28,7 +30,11 @@ import { UserModel } from '../account/user'
28 required: true 30 required: true
29 }, 31 },
30 { 32 {
31 model: VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]), 33 model: VideoModel.scope([
34 VideoModelScopeNames.WITH_ACCOUNT_DETAILS,
35 VideoModelScopeNames.WITH_TAGS,
36 VideoModelScopeNames.WITH_THUMBNAILS
37 ]),
32 required: false 38 required: false
33 } 39 }
34 ] 40 ]
@@ -114,7 +120,7 @@ export class VideoImportModel extends Model<VideoImportModel> {
114 return undefined 120 return undefined
115 } 121 }
116 122
117 static loadAndPopulateVideo (id: number) { 123 static loadAndPopulateVideo (id: number): Bluebird<MVideoImportDefault> {
118 return VideoImportModel.findByPk(id) 124 return VideoImportModel.findByPk(id)
119 } 125 }
120 126
@@ -135,7 +141,7 @@ export class VideoImportModel extends Model<VideoImportModel> {
135 } 141 }
136 } 142 }
137 143
138 return VideoImportModel.findAndCountAll(query) 144 return VideoImportModel.findAndCountAll<MVideoImportDefault>(query)
139 .then(({ rows, count }) => { 145 .then(({ rows, count }) => {
140 return { 146 return {
141 data: rows, 147 data: rows,
@@ -148,7 +154,7 @@ export class VideoImportModel extends Model<VideoImportModel> {
148 return this.targetUrl || this.magnetUri || this.torrentName 154 return this.targetUrl || this.magnetUri || this.torrentName
149 } 155 }
150 156
151 toFormattedJSON (): VideoImport { 157 toFormattedJSON (this: MVideoImportFormattable): VideoImport {
152 const videoFormatOptions = { 158 const videoFormatOptions = {
153 completeDescription: true, 159 completeDescription: true,
154 additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true } 160 additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true }
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts
index dd7653533..a28021313 100644
--- a/server/models/video/video-playlist-element.ts
+++ b/server/models/video/video-playlist-element.ts
@@ -21,10 +21,18 @@ import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
21import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' 21import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object'
22import * as validator from 'validator' 22import * as validator from 'validator'
23import { AggregateOptions, Op, ScopeOptions, Sequelize, Transaction } from 'sequelize' 23import { AggregateOptions, Op, ScopeOptions, Sequelize, Transaction } from 'sequelize'
24import { UserModel } from '../account/user'
25import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model' 24import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model'
26import { AccountModel } from '../account/account' 25import { AccountModel } from '../account/account'
27import { VideoPrivacy } from '../../../shared/models/videos' 26import { VideoPrivacy } from '../../../shared/models/videos'
27import * as Bluebird from 'bluebird'
28import {
29 MVideoPlaylistElement,
30 MVideoPlaylistElementAP,
31 MVideoPlaylistElementFormattable,
32 MVideoPlaylistElementVideoUrlPlaylistPrivacy,
33 MVideoPlaylistVideoThumbnail
34} from '@server/typings/models/video/video-playlist-element'
35import { MUserAccountId } from '@server/typings/models'
28 36
29@Table({ 37@Table({
30 tableName: 'videoPlaylistElement', 38 tableName: 'videoPlaylistElement',
@@ -116,7 +124,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
116 count: number, 124 count: number,
117 videoPlaylistId: number, 125 videoPlaylistId: number,
118 serverAccount: AccountModel, 126 serverAccount: AccountModel,
119 user?: UserModel 127 user?: MUserAccountId
120 }) { 128 }) {
121 const accountIds = [ options.serverAccount.id ] 129 const accountIds = [ options.serverAccount.id ]
122 const videoScope: (ScopeOptions | string)[] = [ 130 const videoScope: (ScopeOptions | string)[] = [
@@ -162,7 +170,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
162 ]).then(([ total, data ]) => ({ total, data })) 170 ]).then(([ total, data ]) => ({ total, data }))
163 } 171 }
164 172
165 static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) { 173 static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number): Bluebird<MVideoPlaylistElement> {
166 const query = { 174 const query = {
167 where: { 175 where: {
168 videoPlaylistId, 176 videoPlaylistId,
@@ -173,11 +181,14 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
173 return VideoPlaylistElementModel.findOne(query) 181 return VideoPlaylistElementModel.findOne(query)
174 } 182 }
175 183
176 static loadById (playlistElementId: number) { 184 static loadById (playlistElementId: number): Bluebird<MVideoPlaylistElement> {
177 return VideoPlaylistElementModel.findByPk(playlistElementId) 185 return VideoPlaylistElementModel.findByPk(playlistElementId)
178 } 186 }
179 187
180 static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) { 188 static loadByPlaylistAndVideoForAP (
189 playlistId: number | string,
190 videoId: number | string
191 ): Bluebird<MVideoPlaylistElementVideoUrlPlaylistPrivacy> {
181 const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } 192 const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId }
182 const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId } 193 const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId }
183 194
@@ -218,7 +229,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
218 }) 229 })
219 } 230 }
220 231
221 static loadFirstElementWithVideoThumbnail (videoPlaylistId: number) { 232 static loadFirstElementWithVideoThumbnail (videoPlaylistId: number): Bluebird<MVideoPlaylistVideoThumbnail> {
222 const query = { 233 const query = {
223 order: getSort('position'), 234 order: getSort('position'),
224 where: { 235 where: {
@@ -290,7 +301,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
290 return VideoPlaylistElementModel.increment({ position: by }, query) 301 return VideoPlaylistElementModel.increment({ position: by }, query)
291 } 302 }
292 303
293 getType (displayNSFW?: boolean, accountId?: number) { 304 getType (this: MVideoPlaylistElementFormattable, displayNSFW?: boolean, accountId?: number) {
294 const video = this.Video 305 const video = this.Video
295 306
296 if (!video) return VideoPlaylistElementType.DELETED 307 if (!video) return VideoPlaylistElementType.DELETED
@@ -306,14 +317,17 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
306 return VideoPlaylistElementType.REGULAR 317 return VideoPlaylistElementType.REGULAR
307 } 318 }
308 319
309 getVideoElement (displayNSFW?: boolean, accountId?: number) { 320 getVideoElement (this: MVideoPlaylistElementFormattable, displayNSFW?: boolean, accountId?: number) {
310 if (!this.Video) return null 321 if (!this.Video) return null
311 if (this.getType(displayNSFW, accountId) !== VideoPlaylistElementType.REGULAR) return null 322 if (this.getType(displayNSFW, accountId) !== VideoPlaylistElementType.REGULAR) return null
312 323
313 return this.Video.toFormattedJSON() 324 return this.Video.toFormattedJSON()
314 } 325 }
315 326
316 toFormattedJSON (options: { displayNSFW?: boolean, accountId?: number } = {}): VideoPlaylistElement { 327 toFormattedJSON (
328 this: MVideoPlaylistElementFormattable,
329 options: { displayNSFW?: boolean, accountId?: number } = {}
330 ): VideoPlaylistElement {
317 return { 331 return {
318 id: this.id, 332 id: this.id,
319 position: this.position, 333 position: this.position,
@@ -326,7 +340,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
326 } 340 }
327 } 341 }
328 342
329 toActivityPubObject (): PlaylistElementObject { 343 toActivityPubObject (this: MVideoPlaylistElementAP): PlaylistElementObject {
330 const base: PlaylistElementObject = { 344 const base: PlaylistElementObject = {
331 id: this.url, 345 id: this.url,
332 type: 'PlaylistElement', 346 type: 'PlaylistElement',
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index c8e97c491..278d80ac0 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -43,6 +43,15 @@ import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-
43import { ThumbnailModel } from './thumbnail' 43import { ThumbnailModel } from './thumbnail'
44import { ActivityIconObject } from '../../../shared/models/activitypub/objects' 44import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
45import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize' 45import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize'
46import * as Bluebird from 'bluebird'
47import {
48 MVideoPlaylistAccountThumbnail, MVideoPlaylistAP,
49 MVideoPlaylistFormattable,
50 MVideoPlaylistFull,
51 MVideoPlaylistFullSummary,
52 MVideoPlaylistIdWithElements
53} from '../../typings/models/video/video-playlist'
54import { MThumbnail } from '../../typings/models/video/thumbnail'
46 55
47enum ScopeNames { 56enum ScopeNames {
48 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', 57 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
@@ -332,7 +341,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
332 }) 341 })
333 } 342 }
334 343
335 static listPlaylistIdsOf (accountId: number, videoIds: number[]) { 344 static listPlaylistIdsOf (accountId: number, videoIds: number[]): Bluebird<MVideoPlaylistIdWithElements[]> {
336 const query = { 345 const query = {
337 attributes: [ 'id' ], 346 attributes: [ 'id' ],
338 where: { 347 where: {
@@ -368,7 +377,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
368 .then(e => !!e) 377 .then(e => !!e)
369 } 378 }
370 379
371 static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction) { 380 static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction): Bluebird<MVideoPlaylistFullSummary> {
372 const where = buildWhereIdOrUUID(id) 381 const where = buildWhereIdOrUUID(id)
373 382
374 const query = { 383 const query = {
@@ -381,7 +390,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
381 .findOne(query) 390 .findOne(query)
382 } 391 }
383 392
384 static loadWithAccountAndChannel (id: number | string, transaction: Transaction) { 393 static loadWithAccountAndChannel (id: number | string, transaction: Transaction): Bluebird<MVideoPlaylistFull> {
385 const where = buildWhereIdOrUUID(id) 394 const where = buildWhereIdOrUUID(id)
386 395
387 const query = { 396 const query = {
@@ -394,7 +403,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
394 .findOne(query) 403 .findOne(query)
395 } 404 }
396 405
397 static loadByUrlAndPopulateAccount (url: string) { 406 static loadByUrlAndPopulateAccount (url: string): Bluebird<MVideoPlaylistAccountThumbnail> {
398 const query = { 407 const query = {
399 where: { 408 where: {
400 url 409 url
@@ -423,7 +432,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
423 return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query) 432 return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query)
424 } 433 }
425 434
426 async setAndSaveThumbnail (thumbnail: ThumbnailModel, t: Transaction) { 435 async setAndSaveThumbnail (thumbnail: MThumbnail, t: Transaction) {
427 thumbnail.videoPlaylistId = this.id 436 thumbnail.videoPlaylistId = this.id
428 437
429 this.Thumbnail = await thumbnail.save({ transaction: t }) 438 this.Thumbnail = await thumbnail.save({ transaction: t })
@@ -471,7 +480,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
471 return isOutdated(this, ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL) 480 return isOutdated(this, ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL)
472 } 481 }
473 482
474 toFormattedJSON (): VideoPlaylist { 483 toFormattedJSON (this: MVideoPlaylistFormattable): VideoPlaylist {
475 return { 484 return {
476 id: this.id, 485 id: this.id,
477 uuid: this.uuid, 486 uuid: this.uuid,
@@ -501,7 +510,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
501 } 510 }
502 } 511 }
503 512
504 toActivityPubObject (page: number, t: Transaction): Promise<PlaylistObject> { 513 toActivityPubObject (this: MVideoPlaylistAP, page: number, t: Transaction): Promise<PlaylistObject> {
505 const handler = (start: number, count: number) => { 514 const handler = (start: number, count: number) => {
506 return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t) 515 return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t)
507 } 516 }
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts
index d8ed64557..9019b401a 100644
--- a/server/models/video/video-share.ts
+++ b/server/models/video/video-share.ts
@@ -8,6 +8,8 @@ import { buildLocalActorIdsIn, throwIfNotValid } from '../utils'
8import { VideoModel } from './video' 8import { VideoModel } from './video'
9import { VideoChannelModel } from './video-channel' 9import { VideoChannelModel } from './video-channel'
10import { Op, Transaction } from 'sequelize' 10import { Op, Transaction } from 'sequelize'
11import { MVideoShareActor, MVideoShareFull } from '../../typings/models/video'
12import { MActorDefault } from '../../typings/models'
11 13
12enum ScopeNames { 14enum ScopeNames {
13 FULL = 'FULL', 15 FULL = 'FULL',
@@ -88,7 +90,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
88 }) 90 })
89 Video: VideoModel 91 Video: VideoModel
90 92
91 static load (actorId: number, videoId: number, t?: Transaction) { 93 static load (actorId: number, videoId: number, t?: Transaction): Bluebird<MVideoShareActor> {
92 return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({ 94 return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({
93 where: { 95 where: {
94 actorId, 96 actorId,
@@ -98,7 +100,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
98 }) 100 })
99 } 101 }
100 102
101 static loadByUrl (url: string, t: Transaction) { 103 static loadByUrl (url: string, t: Transaction): Bluebird<MVideoShareFull> {
102 return VideoShareModel.scope(ScopeNames.FULL).findOne({ 104 return VideoShareModel.scope(ScopeNames.FULL).findOne({
103 where: { 105 where: {
104 url 106 url
@@ -107,7 +109,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
107 }) 109 })
108 } 110 }
109 111
110 static loadActorsByShare (videoId: number, t: Transaction) { 112 static loadActorsByShare (videoId: number, t: Transaction): Bluebird<MActorDefault[]> {
111 const query = { 113 const query = {
112 where: { 114 where: {
113 videoId 115 videoId
@@ -122,10 +124,10 @@ export class VideoShareModel extends Model<VideoShareModel> {
122 } 124 }
123 125
124 return VideoShareModel.scope(ScopeNames.FULL).findAll(query) 126 return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
125 .then(res => res.map(r => r.Actor)) 127 .then((res: MVideoShareFull[]) => res.map(r => r.Actor))
126 } 128 }
127 129
128 static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<ActorModel[]> { 130 static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<MActorDefault[]> {
129 const query = { 131 const query = {
130 attributes: [], 132 attributes: [],
131 include: [ 133 include: [
@@ -163,7 +165,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
163 .then(res => res.map(r => r.Actor)) 165 .then(res => res.map(r => r.Actor))
164 } 166 }
165 167
166 static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<ActorModel[]> { 168 static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<MActorDefault[]> {
167 const query = { 169 const query = {
168 attributes: [], 170 attributes: [],
169 include: [ 171 include: [
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts
index 31dc82c54..0ea90d28c 100644
--- a/server/models/video/video-streaming-playlist.ts
+++ b/server/models/video/video-streaming-playlist.ts
@@ -1,16 +1,16 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, HasMany, Is, Model, Table, UpdatedAt, DataType } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' 2import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
3import { throwIfNotValid } from '../utils' 3import { throwIfNotValid } from '../utils'
4import { VideoModel } from './video' 4import { VideoModel } from './video'
5import { VideoRedundancyModel } from '../redundancy/video-redundancy' 5import { VideoRedundancyModel } from '../redundancy/video-redundancy'
6import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 6import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
7import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 7import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
8import { CONSTRAINTS_FIELDS, STATIC_PATHS, P2P_MEDIA_LOADER_PEER_VERSION } from '../../initializers/constants' 8import { CONSTRAINTS_FIELDS, P2P_MEDIA_LOADER_PEER_VERSION, STATIC_PATHS } from '../../initializers/constants'
9import { VideoFileModel } from './video-file'
10import { join } from 'path' 9import { join } from 'path'
11import { sha1 } from '../../helpers/core-utils' 10import { sha1 } from '../../helpers/core-utils'
12import { isArrayOf } from '../../helpers/custom-validators/misc' 11import { isArrayOf } from '../../helpers/custom-validators/misc'
13import { QueryTypes, Op } from 'sequelize' 12import { Op, QueryTypes } from 'sequelize'
13import { MStreamingPlaylist, MVideoFile } from '@server/typings/models'
14 14
15@Table({ 15@Table({
16 tableName: 'videoStreamingPlaylist', 16 tableName: 'videoStreamingPlaylist',
@@ -91,7 +91,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod
91 .then(results => results.length === 1) 91 .then(results => results.length === 1)
92 } 92 }
93 93
94 static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: VideoFileModel[]) { 94 static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: MVideoFile[]) {
95 const hashes: string[] = [] 95 const hashes: string[] = []
96 96
97 // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L115 97 // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L115
@@ -165,7 +165,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod
165 return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid 165 return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid
166 } 166 }
167 167
168 hasSameUniqueKeysThan (other: VideoStreamingPlaylistModel) { 168 hasSameUniqueKeysThan (other: MStreamingPlaylist) {
169 return this.type === other.type && 169 return this.type === other.type &&
170 this.videoId === other.videoId 170 this.videoId === other.videoId
171 } 171 }
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index b59df397d..6856dcd9f 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -36,7 +36,7 @@ import {
36 Table, 36 Table,
37 UpdatedAt 37 UpdatedAt
38} from 'sequelize-typescript' 38} from 'sequelize-typescript'
39import { UserRight, VideoPrivacy, VideoResolution, VideoState } from '../../../shared' 39import { UserRight, VideoPrivacy, VideoState } from '../../../shared'
40import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' 40import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
41import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' 41import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos'
42import { VideoFilter } from '../../../shared/models/videos/video-query.type' 42import { VideoFilter } from '../../../shared/models/videos/video-query.type'
@@ -111,7 +111,6 @@ import {
111 videoModelToFormattedJSON 111 videoModelToFormattedJSON
112} from './video-format-utils' 112} from './video-format-utils'
113import { UserVideoHistoryModel } from '../account/user-video-history' 113import { UserVideoHistoryModel } from '../account/user-video-history'
114import { UserModel } from '../account/user'
115import { VideoImportModel } from './video-import' 114import { VideoImportModel } from './video-import'
116import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 115import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
117import { VideoPlaylistElementModel } from './video-playlist-element' 116import { VideoPlaylistElementModel } from './video-playlist-element'
@@ -120,6 +119,29 @@ import { ThumbnailModel } from './thumbnail'
120import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' 119import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
121import { createTorrentPromise } from '../../helpers/webtorrent' 120import { createTorrentPromise } from '../../helpers/webtorrent'
122import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 121import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
122import {
123 MChannel,
124 MChannelAccountDefault,
125 MChannelId,
126 MUserAccountId,
127 MUserId,
128 MVideoAccountLight,
129 MVideoAccountLightBlacklistAllFiles,
130 MVideoAP,
131 MVideoDetails,
132 MVideoFormattable,
133 MVideoFormattableDetails,
134 MVideoForUser,
135 MVideoFullLight,
136 MVideoIdThumbnail,
137 MVideoThumbnail,
138 MVideoThumbnailBlacklist,
139 MVideoWithAllFiles,
140 MVideoWithFile,
141 MVideoWithRights
142} from '../../typings/models'
143import { MVideoFile, MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file'
144import { MThumbnail } from '../../typings/models/video/thumbnail'
123 145
124// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation 146// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
125const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [ 147const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [
@@ -232,8 +254,8 @@ export type AvailableForListIDsOptions = {
232 videoPlaylistId?: number 254 videoPlaylistId?: number
233 255
234 trendingDays?: number 256 trendingDays?: number
235 user?: UserModel, 257 user?: MUserAccountId
236 historyOfUser?: UserModel 258 historyOfUser?: MUserId
237 259
238 baseWhere?: WhereOptions[] 260 baseWhere?: WhereOptions[]
239} 261}
@@ -446,13 +468,15 @@ export type AvailableForListIDsOptions = {
446 // FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN() 468 // FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN()
447 if (options.tagsAllOf || options.tagsOneOf) { 469 if (options.tagsAllOf || options.tagsOneOf) {
448 if (options.tagsOneOf) { 470 if (options.tagsOneOf) {
471 const tagsOneOfLower = options.tagsOneOf.map(t => t.toLowerCase())
472
449 whereAnd.push({ 473 whereAnd.push({
450 id: { 474 id: {
451 [ Op.in ]: Sequelize.literal( 475 [ Op.in ]: Sequelize.literal(
452 '(' + 476 '(' +
453 'SELECT "videoId" FROM "videoTag" ' + 477 'SELECT "videoId" FROM "videoTag" ' +
454 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 478 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
455 'WHERE "tag"."name" IN (' + createSafeIn(VideoModel, options.tagsOneOf) + ')' + 479 'WHERE lower("tag"."name") IN (' + createSafeIn(VideoModel, tagsOneOfLower) + ')' +
456 ')' 480 ')'
457 ) 481 )
458 } 482 }
@@ -460,14 +484,16 @@ export type AvailableForListIDsOptions = {
460 } 484 }
461 485
462 if (options.tagsAllOf) { 486 if (options.tagsAllOf) {
487 const tagsAllOfLower = options.tagsAllOf.map(t => t.toLowerCase())
488
463 whereAnd.push({ 489 whereAnd.push({
464 id: { 490 id: {
465 [ Op.in ]: Sequelize.literal( 491 [ Op.in ]: Sequelize.literal(
466 '(' + 492 '(' +
467 'SELECT "videoId" FROM "videoTag" ' + 493 'SELECT "videoId" FROM "videoTag" ' +
468 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 494 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
469 'WHERE "tag"."name" IN (' + createSafeIn(VideoModel, options.tagsAllOf) + ')' + 495 'WHERE lower("tag"."name") IN (' + createSafeIn(VideoModel, tagsAllOfLower) + ')' +
470 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + options.tagsAllOf.length + 496 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + tagsAllOfLower.length +
471 ')' 497 ')'
472 ) 498 )
473 } 499 }
@@ -634,7 +660,7 @@ export type AvailableForListIDsOptions = {
634 [ ScopeNames.WITH_BLACKLISTED ]: { 660 [ ScopeNames.WITH_BLACKLISTED ]: {
635 include: [ 661 include: [
636 { 662 {
637 attributes: [ 'id', 'reason' ], 663 attributes: [ 'id', 'reason', 'unfederated' ],
638 model: VideoBlacklistModel, 664 model: VideoBlacklistModel,
639 required: false 665 required: false
640 } 666 }
@@ -989,18 +1015,16 @@ export class VideoModel extends Model<VideoModel> {
989 VideoCaptions: VideoCaptionModel[] 1015 VideoCaptions: VideoCaptionModel[]
990 1016
991 @BeforeDestroy 1017 @BeforeDestroy
992 static async sendDelete (instance: VideoModel, options) { 1018 static async sendDelete (instance: MVideoAccountLight, options) {
993 if (instance.isOwned()) { 1019 if (instance.isOwned()) {
994 if (!instance.VideoChannel) { 1020 if (!instance.VideoChannel) {
995 instance.VideoChannel = await instance.$get('VideoChannel', { 1021 instance.VideoChannel = await instance.$get('VideoChannel', {
996 include: [ 1022 include: [
997 { 1023 ActorModel,
998 model: AccountModel, 1024 AccountModel
999 include: [ ActorModel ]
1000 }
1001 ], 1025 ],
1002 transaction: options.transaction 1026 transaction: options.transaction
1003 }) as VideoChannelModel 1027 }) as MChannelAccountDefault
1004 } 1028 }
1005 1029
1006 return sendDeleteVideo(instance, options.transaction) 1030 return sendDeleteVideo(instance, options.transaction)
@@ -1039,7 +1063,7 @@ export class VideoModel extends Model<VideoModel> {
1039 return undefined 1063 return undefined
1040 } 1064 }
1041 1065
1042 static listLocal () { 1066 static listLocal (): Bluebird<MVideoWithAllFiles[]> {
1043 const query = { 1067 const query = {
1044 where: { 1068 where: {
1045 remote: false 1069 remote: false
@@ -1159,7 +1183,7 @@ export class VideoModel extends Model<VideoModel> {
1159 }) 1183 })
1160 } 1184 }
1161 1185
1162 static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { 1186 static listUserVideosForApi (accountId: number, start: number, count: number, sort: string) {
1163 function buildBaseQuery (): FindOptions { 1187 function buildBaseQuery (): FindOptions {
1164 return { 1188 return {
1165 offset: start, 1189 offset: start,
@@ -1192,16 +1216,9 @@ export class VideoModel extends Model<VideoModel> {
1192 ScopeNames.WITH_THUMBNAILS 1216 ScopeNames.WITH_THUMBNAILS
1193 ] 1217 ]
1194 1218
1195 if (withFiles === true) {
1196 findQuery.include.push({
1197 model: VideoFileModel.unscoped(),
1198 required: true
1199 })
1200 }
1201
1202 return Promise.all([ 1219 return Promise.all([
1203 VideoModel.count(countQuery), 1220 VideoModel.count(countQuery),
1204 VideoModel.scope(findScopes).findAll(findQuery) 1221 VideoModel.scope(findScopes).findAll<MVideoForUser>(findQuery)
1205 ]).then(([ count, rows ]) => { 1222 ]).then(([ count, rows ]) => {
1206 return { 1223 return {
1207 data: rows, 1224 data: rows,
@@ -1228,8 +1245,8 @@ export class VideoModel extends Model<VideoModel> {
1228 followerActorId?: number 1245 followerActorId?: number
1229 videoPlaylistId?: number, 1246 videoPlaylistId?: number,
1230 trendingDays?: number, 1247 trendingDays?: number,
1231 user?: UserModel, 1248 user?: MUserAccountId,
1232 historyOfUser?: UserModel 1249 historyOfUser?: MUserId
1233 }, countVideos = true) { 1250 }, countVideos = true) {
1234 if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { 1251 if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
1235 throw new Error('Try to filter all-local but no user has not the see all videos right') 1252 throw new Error('Try to filter all-local but no user has not the see all videos right')
@@ -1294,7 +1311,7 @@ export class VideoModel extends Model<VideoModel> {
1294 tagsAllOf?: string[] 1311 tagsAllOf?: string[]
1295 durationMin?: number // seconds 1312 durationMin?: number // seconds
1296 durationMax?: number // seconds 1313 durationMax?: number // seconds
1297 user?: UserModel, 1314 user?: MUserAccountId,
1298 filter?: VideoFilter 1315 filter?: VideoFilter
1299 }) { 1316 }) {
1300 const whereAnd = [] 1317 const whereAnd = []
@@ -1387,7 +1404,7 @@ export class VideoModel extends Model<VideoModel> {
1387 return VideoModel.getAvailableForApi(query, queryOptions) 1404 return VideoModel.getAvailableForApi(query, queryOptions)
1388 } 1405 }
1389 1406
1390 static load (id: number | string, t?: Transaction) { 1407 static load (id: number | string, t?: Transaction): Bluebird<MVideoThumbnail> {
1391 const where = buildWhereIdOrUUID(id) 1408 const where = buildWhereIdOrUUID(id)
1392 const options = { 1409 const options = {
1393 where, 1410 where,
@@ -1397,7 +1414,20 @@ export class VideoModel extends Model<VideoModel> {
1397 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) 1414 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
1398 } 1415 }
1399 1416
1400 static loadWithRights (id: number | string, t?: Transaction) { 1417 static loadWithBlacklist (id: number | string, t?: Transaction): Bluebird<MVideoThumbnailBlacklist> {
1418 const where = buildWhereIdOrUUID(id)
1419 const options = {
1420 where,
1421 transaction: t
1422 }
1423
1424 return VideoModel.scope([
1425 ScopeNames.WITH_THUMBNAILS,
1426 ScopeNames.WITH_BLACKLISTED
1427 ]).findOne(options)
1428 }
1429
1430 static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> {
1401 const where = buildWhereIdOrUUID(id) 1431 const where = buildWhereIdOrUUID(id)
1402 const options = { 1432 const options = {
1403 where, 1433 where,
@@ -1411,7 +1441,7 @@ export class VideoModel extends Model<VideoModel> {
1411 ]).findOne(options) 1441 ]).findOne(options)
1412 } 1442 }
1413 1443
1414 static loadOnlyId (id: number | string, t?: Transaction) { 1444 static loadOnlyId (id: number | string, t?: Transaction): Bluebird<MVideoIdThumbnail> {
1415 const where = buildWhereIdOrUUID(id) 1445 const where = buildWhereIdOrUUID(id)
1416 1446
1417 const options = { 1447 const options = {
@@ -1423,7 +1453,7 @@ export class VideoModel extends Model<VideoModel> {
1423 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) 1453 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
1424 } 1454 }
1425 1455
1426 static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean) { 1456 static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean): Bluebird<MVideoWithAllFiles> {
1427 const where = buildWhereIdOrUUID(id) 1457 const where = buildWhereIdOrUUID(id)
1428 1458
1429 const query = { 1459 const query = {
@@ -1439,7 +1469,7 @@ export class VideoModel extends Model<VideoModel> {
1439 ]).findOne(query) 1469 ]).findOne(query)
1440 } 1470 }
1441 1471
1442 static loadByUUID (uuid: string) { 1472 static loadByUUID (uuid: string): Bluebird<MVideoThumbnail> {
1443 const options = { 1473 const options = {
1444 where: { 1474 where: {
1445 uuid 1475 uuid
@@ -1449,7 +1479,7 @@ export class VideoModel extends Model<VideoModel> {
1449 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) 1479 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
1450 } 1480 }
1451 1481
1452 static loadByUrl (url: string, transaction?: Transaction) { 1482 static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoThumbnail> {
1453 const query: FindOptions = { 1483 const query: FindOptions = {
1454 where: { 1484 where: {
1455 url 1485 url
@@ -1460,7 +1490,7 @@ export class VideoModel extends Model<VideoModel> {
1460 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) 1490 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query)
1461 } 1491 }
1462 1492
1463 static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction) { 1493 static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> {
1464 const query: FindOptions = { 1494 const query: FindOptions = {
1465 where: { 1495 where: {
1466 url 1496 url
@@ -1472,11 +1502,12 @@ export class VideoModel extends Model<VideoModel> {
1472 ScopeNames.WITH_ACCOUNT_DETAILS, 1502 ScopeNames.WITH_ACCOUNT_DETAILS,
1473 ScopeNames.WITH_FILES, 1503 ScopeNames.WITH_FILES,
1474 ScopeNames.WITH_STREAMING_PLAYLISTS, 1504 ScopeNames.WITH_STREAMING_PLAYLISTS,
1475 ScopeNames.WITH_THUMBNAILS 1505 ScopeNames.WITH_THUMBNAILS,
1506 ScopeNames.WITH_BLACKLISTED
1476 ]).findOne(query) 1507 ]).findOne(query)
1477 } 1508 }
1478 1509
1479 static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number) { 1510 static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Bluebird<MVideoFullLight> {
1480 const where = buildWhereIdOrUUID(id) 1511 const where = buildWhereIdOrUUID(id)
1481 1512
1482 const options = { 1513 const options = {
@@ -1508,7 +1539,7 @@ export class VideoModel extends Model<VideoModel> {
1508 id: number | string, 1539 id: number | string,
1509 t?: Transaction, 1540 t?: Transaction,
1510 userId?: number 1541 userId?: number
1511 }) { 1542 }): Bluebird<MVideoDetails> {
1512 const { id, t, userId } = parameters 1543 const { id, t, userId } = parameters
1513 const where = buildWhereIdOrUUID(id) 1544 const where = buildWhereIdOrUUID(id)
1514 1545
@@ -1586,7 +1617,7 @@ export class VideoModel extends Model<VideoModel> {
1586 .then(results => results.length === 1) 1617 .then(results => results.length === 1)
1587 } 1618 }
1588 1619
1589 static bulkUpdateSupportField (videoChannel: VideoChannelModel, t: Transaction) { 1620 static bulkUpdateSupportField (videoChannel: MChannel, t: Transaction) {
1590 const options = { 1621 const options = {
1591 where: { 1622 where: {
1592 channelId: videoChannel.id 1623 channelId: videoChannel.id
@@ -1597,7 +1628,7 @@ export class VideoModel extends Model<VideoModel> {
1597 return VideoModel.update({ support: videoChannel.support }, options) 1628 return VideoModel.update({ support: videoChannel.support }, options)
1598 } 1629 }
1599 1630
1600 static getAllIdsFromChannel (videoChannel: VideoChannelModel) { 1631 static getAllIdsFromChannel (videoChannel: MChannelId): Bluebird<number[]> {
1601 const query = { 1632 const query = {
1602 attributes: [ 'id' ], 1633 attributes: [ 'id' ],
1603 where: { 1634 where: {
@@ -1756,20 +1787,20 @@ export class VideoModel extends Model<VideoModel> {
1756 this.VideoChannel.Account.isBlocked() 1787 this.VideoChannel.Account.isBlocked()
1757 } 1788 }
1758 1789
1759 getOriginalFile () { 1790 getOriginalFile <T extends MVideoWithFile> (this: T) {
1760 if (Array.isArray(this.VideoFiles) === false) return undefined 1791 if (Array.isArray(this.VideoFiles) === false) return undefined
1761 1792
1762 // The original file is the file that have the higher resolution 1793 // The original file is the file that have the higher resolution
1763 return maxBy(this.VideoFiles, file => file.resolution) 1794 return maxBy(this.VideoFiles, file => file.resolution)
1764 } 1795 }
1765 1796
1766 getFile (resolution: number) { 1797 getFile <T extends MVideoWithFile> (this: T, resolution: number) {
1767 if (Array.isArray(this.VideoFiles) === false) return undefined 1798 if (Array.isArray(this.VideoFiles) === false) return undefined
1768 1799
1769 return this.VideoFiles.find(f => f.resolution === resolution) 1800 return this.VideoFiles.find(f => f.resolution === resolution)
1770 } 1801 }
1771 1802
1772 async addAndSaveThumbnail (thumbnail: ThumbnailModel, transaction: Transaction) { 1803 async addAndSaveThumbnail (thumbnail: MThumbnail, transaction: Transaction) {
1773 thumbnail.videoId = this.id 1804 thumbnail.videoId = this.id
1774 1805
1775 const savedThumbnail = await thumbnail.save({ transaction }) 1806 const savedThumbnail = await thumbnail.save({ transaction })
@@ -1782,7 +1813,7 @@ export class VideoModel extends Model<VideoModel> {
1782 this.Thumbnails.push(savedThumbnail) 1813 this.Thumbnails.push(savedThumbnail)
1783 } 1814 }
1784 1815
1785 getVideoFilename (videoFile: VideoFileModel) { 1816 getVideoFilename (videoFile: MVideoFile) {
1786 return this.uuid + '-' + videoFile.resolution + videoFile.extname 1817 return this.uuid + '-' + videoFile.resolution + videoFile.extname
1787 } 1818 }
1788 1819
@@ -1806,7 +1837,7 @@ export class VideoModel extends Model<VideoModel> {
1806 return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW) 1837 return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW)
1807 } 1838 }
1808 1839
1809 getTorrentFileName (videoFile: VideoFileModel) { 1840 getTorrentFileName (videoFile: MVideoFile) {
1810 const extension = '.torrent' 1841 const extension = '.torrent'
1811 return this.uuid + '-' + videoFile.resolution + extension 1842 return this.uuid + '-' + videoFile.resolution + extension
1812 } 1843 }
@@ -1815,15 +1846,15 @@ export class VideoModel extends Model<VideoModel> {
1815 return this.remote === false 1846 return this.remote === false
1816 } 1847 }
1817 1848
1818 getTorrentFilePath (videoFile: VideoFileModel) { 1849 getTorrentFilePath (videoFile: MVideoFile) {
1819 return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) 1850 return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
1820 } 1851 }
1821 1852
1822 getVideoFilePath (videoFile: VideoFileModel) { 1853 getVideoFilePath (videoFile: MVideoFile) {
1823 return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) 1854 return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
1824 } 1855 }
1825 1856
1826 async createTorrentAndSetInfoHash (videoFile: VideoFileModel) { 1857 async createTorrentAndSetInfoHash (videoFile: MVideoFile) {
1827 const options = { 1858 const options = {
1828 // Keep the extname, it's used by the client to stream the file inside a web browser 1859 // Keep the extname, it's used by the client to stream the file inside a web browser
1829 name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`, 1860 name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`,
@@ -1869,11 +1900,11 @@ export class VideoModel extends Model<VideoModel> {
1869 return join(LAZY_STATIC_PATHS.PREVIEWS, preview.filename) 1900 return join(LAZY_STATIC_PATHS.PREVIEWS, preview.filename)
1870 } 1901 }
1871 1902
1872 toFormattedJSON (options?: VideoFormattingJSONOptions): Video { 1903 toFormattedJSON (this: MVideoFormattable, options?: VideoFormattingJSONOptions): Video {
1873 return videoModelToFormattedJSON(this, options) 1904 return videoModelToFormattedJSON(this, options)
1874 } 1905 }
1875 1906
1876 toFormattedDetailsJSON (): VideoDetails { 1907 toFormattedDetailsJSON (this: MVideoFormattableDetails): VideoDetails {
1877 return videoModelToFormattedDetailsJSON(this) 1908 return videoModelToFormattedDetailsJSON(this)
1878 } 1909 }
1879 1910
@@ -1881,7 +1912,7 @@ export class VideoModel extends Model<VideoModel> {
1881 return videoFilesModelToFormattedJSON(this, this.VideoFiles) 1912 return videoFilesModelToFormattedJSON(this, this.VideoFiles)
1882 } 1913 }
1883 1914
1884 toActivityPubObject (): VideoTorrentObject { 1915 toActivityPubObject (this: MVideoAP): VideoTorrentObject {
1885 return videoModelToActivityPubObject(this) 1916 return videoModelToActivityPubObject(this)
1886 } 1917 }
1887 1918
@@ -1908,7 +1939,7 @@ export class VideoModel extends Model<VideoModel> {
1908 return this.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) 1939 return this.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
1909 } 1940 }
1910 1941
1911 removeFile (videoFile: VideoFileModel, isRedundancy = false) { 1942 removeFile (videoFile: MVideoFile, isRedundancy = false) {
1912 const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR 1943 const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR
1913 1944
1914 const filePath = join(baseDir, this.getVideoFilename(videoFile)) 1945 const filePath = join(baseDir, this.getVideoFilename(videoFile))
@@ -1916,7 +1947,7 @@ export class VideoModel extends Model<VideoModel> {
1916 .catch(err => logger.warn('Cannot delete file %s.', filePath, { err })) 1947 .catch(err => logger.warn('Cannot delete file %s.', filePath, { err }))
1917 } 1948 }
1918 1949
1919 removeTorrent (videoFile: VideoFileModel) { 1950 removeTorrent (videoFile: MVideoFile) {
1920 const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) 1951 const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
1921 return remove(torrentPath) 1952 return remove(torrentPath)
1922 .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) 1953 .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err }))
@@ -1957,7 +1988,7 @@ export class VideoModel extends Model<VideoModel> {
1957 return { baseUrlHttp, baseUrlWs } 1988 return { baseUrlHttp, baseUrlWs }
1958 } 1989 }
1959 1990
1960 generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { 1991 generateMagnetUri (videoFile: MVideoFileRedundanciesOpt, baseUrlHttp: string, baseUrlWs: string) {
1961 const xs = this.getTorrentUrl(videoFile, baseUrlHttp) 1992 const xs = this.getTorrentUrl(videoFile, baseUrlHttp)
1962 const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs) 1993 const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs)
1963 let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] 1994 let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ]
@@ -1980,27 +2011,27 @@ export class VideoModel extends Model<VideoModel> {
1980 return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] 2011 return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ]
1981 } 2012 }
1982 2013
1983 getTorrentUrl (videoFile: VideoFileModel, baseUrlHttp: string) { 2014 getTorrentUrl (videoFile: MVideoFile, baseUrlHttp: string) {
1984 return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) 2015 return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile)
1985 } 2016 }
1986 2017
1987 getTorrentDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { 2018 getTorrentDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) {
1988 return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile) 2019 return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile)
1989 } 2020 }
1990 2021
1991 getVideoFileUrl (videoFile: VideoFileModel, baseUrlHttp: string) { 2022 getVideoFileUrl (videoFile: MVideoFile, baseUrlHttp: string) {
1992 return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) 2023 return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile)
1993 } 2024 }
1994 2025
1995 getVideoRedundancyUrl (videoFile: VideoFileModel, baseUrlHttp: string) { 2026 getVideoRedundancyUrl (videoFile: MVideoFile, baseUrlHttp: string) {
1996 return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile) 2027 return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile)
1997 } 2028 }
1998 2029
1999 getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { 2030 getVideoFileDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) {
2000 return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) 2031 return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile)
2001 } 2032 }
2002 2033
2003 getBandwidthBits (videoFile: VideoFileModel) { 2034 getBandwidthBits (videoFile: MVideoFile) {
2004 return Math.ceil((videoFile.size * 8) / this.duration) 2035 return Math.ceil((videoFile.size * 8) / this.duration)
2005 } 2036 }
2006} 2037}
diff --git a/server/tests/api/activitypub/helpers.ts b/server/tests/api/activitypub/helpers.ts
index 365d0e1ae..0d1f154fe 100644
--- a/server/tests/api/activitypub/helpers.ts
+++ b/server/tests/api/activitypub/helpers.ts
@@ -53,19 +53,6 @@ describe('Test activity pub helpers', function () {
53 expect(result).to.be.false 53 expect(result).to.be.false
54 }) 54 })
55 55
56 it('Should fail with an invalid PeerTube URL', async function () {
57 const keys = require('./json/peertube/keys.json')
58 const body = require('./json/peertube/announce-without-context.json')
59
60 const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey }
61 const signedBody = await buildSignedActivity(actorSignature as any, body)
62
63 const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9003/accounts/peertube' }
64 const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
65
66 expect(result).to.be.false
67 })
68
69 it('Should succeed with a valid PeerTube signature', async function () { 56 it('Should succeed with a valid PeerTube signature', async function () {
70 const keys = require('./json/peertube/keys.json') 57 const keys = require('./json/peertube/keys.json')
71 const body = require('./json/peertube/announce-without-context.json') 58 const body = require('./json/peertube/announce-without-context.json')
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts
index c06200ffe..a3e05156b 100644
--- a/server/tests/api/search/search-videos.ts
+++ b/server/tests/api/search/search-videos.ts
@@ -206,7 +206,7 @@ describe('Test videos search', function () {
206 const query = { 206 const query = {
207 search: '9999', 207 search: '9999',
208 categoryOneOf: [ 1 ], 208 categoryOneOf: [ 1 ],
209 tagsOneOf: [ 'aaaa', 'ffff' ] 209 tagsOneOf: [ 'aAaa', 'ffff' ]
210 } 210 }
211 const res1 = await advancedVideosSearch(server.url, query) 211 const res1 = await advancedVideosSearch(server.url, query)
212 expect(res1.body.total).to.equal(2) 212 expect(res1.body.total).to.equal(2)
@@ -219,15 +219,15 @@ describe('Test videos search', function () {
219 const query = { 219 const query = {
220 search: '9999', 220 search: '9999',
221 categoryOneOf: [ 1 ], 221 categoryOneOf: [ 1 ],
222 tagsAllOf: [ 'cccc' ] 222 tagsAllOf: [ 'CCcc' ]
223 } 223 }
224 const res1 = await advancedVideosSearch(server.url, query) 224 const res1 = await advancedVideosSearch(server.url, query)
225 expect(res1.body.total).to.equal(2) 225 expect(res1.body.total).to.equal(2)
226 226
227 const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'blabla' ] })) 227 const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'blAbla' ] }))
228 expect(res2.body.total).to.equal(0) 228 expect(res2.body.total).to.equal(0)
229 229
230 const res3 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'bbbb', 'cccc' ] })) 230 const res3 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'bbbb', 'CCCC' ] }))
231 expect(res3.body.total).to.equal(1) 231 expect(res3.body.total).to.equal(1)
232 }) 232 })
233 233
diff --git a/server/tests/api/videos/video-abuse.ts b/server/tests/api/videos/video-abuse.ts
index a2f3ee161..0cd6f22c7 100644
--- a/server/tests/api/videos/video-abuse.ts
+++ b/server/tests/api/videos/video-abuse.ts
@@ -17,6 +17,12 @@ import {
17} from '../../../../shared/extra-utils/index' 17} from '../../../../shared/extra-utils/index'
18import { doubleFollow } from '../../../../shared/extra-utils/server/follows' 18import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
19import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 19import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
20import {
21 addAccountToServerBlocklist,
22 addServerToServerBlocklist,
23 removeAccountFromServerBlocklist,
24 removeServerFromServerBlocklist
25} from '../../../../shared/extra-utils/users/blocklist'
20 26
21const expect = chai.expect 27const expect = chai.expect
22 28
@@ -163,13 +169,76 @@ describe('Test video abuses', function () {
163 expect(res.body.data[0].moderationComment).to.equal('It is valid') 169 expect(res.body.data[0].moderationComment).to.equal('It is valid')
164 }) 170 })
165 171
172 it('Should hide video abuses from blocked accounts', async function () {
173 this.timeout(10000)
174
175 {
176 await reportVideoAbuse(servers[1].url, servers[1].accessToken, servers[0].video.uuid, 'will mute this')
177 await waitJobs(servers)
178
179 const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
180 expect(res.body.total).to.equal(3)
181 }
182
183 const accountToBlock = 'root@localhost:' + servers[1].port
184
185 {
186 await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock)
187
188 const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken)
189 expect(res.body.total).to.equal(2)
190
191 const abuse = res.body.data.find(a => a.reason === 'will mute this')
192 expect(abuse).to.be.undefined
193 }
194
195 {
196 await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock)
197
198 const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken)
199 expect(res.body.total).to.equal(3)
200 }
201 })
202
203 it('Should hide video abuses from blocked servers', async function () {
204 const serverToBlock = servers[1].host
205
206 {
207 await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, servers[1].host)
208
209 const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken)
210 expect(res.body.total).to.equal(2)
211
212 const abuse = res.body.data.find(a => a.reason === 'will mute this')
213 expect(abuse).to.be.undefined
214 }
215
216 {
217 await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, serverToBlock)
218
219 const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken)
220 expect(res.body.total).to.equal(3)
221 }
222 })
223
166 it('Should delete the video abuse', async function () { 224 it('Should delete the video abuse', async function () {
225 this.timeout(10000)
226
167 await deleteVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id) 227 await deleteVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id)
168 228
169 const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) 229 await waitJobs(servers)
170 expect(res.body.total).to.equal(0) 230
171 expect(res.body.data).to.be.an('array') 231 {
172 expect(res.body.data.length).to.equal(0) 232 const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken)
233 expect(res.body.total).to.equal(1)
234 expect(res.body.data.length).to.equal(1)
235 expect(res.body.data[0].id).to.not.equal(abuseServer2.id)
236 }
237
238 {
239 const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
240 expect(res.body.total).to.equal(3)
241 }
173 }) 242 })
174 243
175 after(async function () { 244 after(async function () {
diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts
index 3a3add71b..64ee2355a 100644
--- a/server/tests/api/videos/video-change-ownership.ts
+++ b/server/tests/api/videos/video-change-ownership.ts
@@ -191,7 +191,7 @@ describe('Test video change ownership - nominal', function () {
191 await waitJobs(servers) 191 await waitJobs(servers)
192 }) 192 })
193 193
194 it('Should have video channel updated', async function () { 194 it('Should have the channel of the video updated', async function () {
195 for (const server of servers) { 195 for (const server of servers) {
196 const res = await getVideo(server.url, servers[0].video.uuid) 196 const res = await getVideo(server.url, servers[0].video.uuid)
197 197
diff --git a/server/tools/cli.ts b/server/tools/cli.ts
index 8599a270f..58e2445ac 100644
--- a/server/tools/cli.ts
+++ b/server/tools/cli.ts
@@ -5,6 +5,7 @@ import { root } from '../../shared/extra-utils/miscs/miscs'
5import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels' 5import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels'
6import { Command } from 'commander' 6import { Command } from 'commander'
7import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' 7import { VideoChannel, VideoPrivacy } from '../../shared/models/videos'
8import { createLogger, format, transports } from 'winston'
8 9
9let configName = 'PeerTube/CLI' 10let configName = 'PeerTube/CLI'
10if (isTestInstance()) configName += `-${getAppNumber()}` 11if (isTestInstance()) configName += `-${getAppNumber()}`
@@ -119,6 +120,7 @@ function buildCommonVideoOptions (command: Command) {
119 .option('-m, --comments-enabled', 'Enable comments') 120 .option('-m, --comments-enabled', 'Enable comments')
120 .option('-s, --support <support>', 'Video support text') 121 .option('-s, --support <support>', 'Video support text')
121 .option('-w, --wait-transcoding', 'Wait transcoding before publishing the video') 122 .option('-w, --wait-transcoding', 'Wait transcoding before publishing the video')
123 .option('-v, --verbose <verbose>', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info')
122} 124}
123 125
124async function buildVideoAttributesFromCommander (url: string, command: Command, defaultAttributes: any = {}) { 126async function buildVideoAttributesFromCommander (url: string, command: Command, defaultAttributes: any = {}) {
@@ -175,11 +177,42 @@ function getServerCredentials (program: any) {
175 }) 177 })
176} 178}
177 179
180function getLogger (logLevel = 'info') {
181 const logLevels = {
182 0: 0,
183 error: 0,
184 1: 1,
185 warn: 1,
186 2: 2,
187 info: 2,
188 3: 3,
189 verbose: 3,
190 4: 4,
191 debug: 4
192 }
193
194 const logger = createLogger({
195 levels: logLevels,
196 format: format.combine(
197 format.splat(),
198 format.simple()
199 ),
200 transports: [
201 new (transports.Console)({
202 level: logLevel
203 })
204 ]
205 })
206
207 return logger
208}
209
178// --------------------------------------------------------------------------- 210// ---------------------------------------------------------------------------
179 211
180export { 212export {
181 version, 213 version,
182 config, 214 config,
215 getLogger,
183 getSettings, 216 getSettings,
184 getNetrc, 217 getNetrc,
185 getRemoteObjectOrDie, 218 getRemoteObjectOrDie,
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts
index 0ebfa7442..fcb90cca3 100644
--- a/server/tools/peertube-import-videos.ts
+++ b/server/tools/peertube-import-videos.ts
@@ -8,10 +8,11 @@ import { CONSTRAINTS_FIELDS } from '../initializers/constants'
8import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../../shared/extra-utils/index' 8import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../../shared/extra-utils/index'
9import { truncate } from 'lodash' 9import { truncate } from 'lodash'
10import * as prompt from 'prompt' 10import * as prompt from 'prompt'
11import { accessSync, constants } from 'fs'
11import { remove } from 'fs-extra' 12import { remove } from 'fs-extra'
12import { sha256 } from '../helpers/core-utils' 13import { sha256 } from '../helpers/core-utils'
13import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' 14import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl'
14import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli' 15import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials, getLogger } from './cli'
15 16
16type UserInfo = { 17type UserInfo = {
17 username: string 18 username: string
@@ -19,7 +20,6 @@ type UserInfo = {
19} 20}
20 21
21const processOptions = { 22const processOptions = {
22 cwd: __dirname,
23 maxBuffer: Infinity 23 maxBuffer: Infinity
24} 24}
25 25
@@ -35,15 +35,23 @@ command
35 .option('--target-url <targetUrl>', 'Video target URL') 35 .option('--target-url <targetUrl>', 'Video target URL')
36 .option('--since <since>', 'Publication date (inclusive) since which the videos can be imported (YYYY-MM-DD)', parseDate) 36 .option('--since <since>', 'Publication date (inclusive) since which the videos can be imported (YYYY-MM-DD)', parseDate)
37 .option('--until <until>', 'Publication date (inclusive) until which the videos can be imported (YYYY-MM-DD)', parseDate) 37 .option('--until <until>', 'Publication date (inclusive) until which the videos can be imported (YYYY-MM-DD)', parseDate)
38 .option('-v, --verbose', 'Verbose mode') 38 .option('--first <first>', 'Process first n elements of returned playlist')
39 .option('--last <last>', 'Process last n elements of returned playlist')
40 .option('-T, --tmpdir <tmpdir>', 'Working directory', __dirname)
39 .parse(process.argv) 41 .parse(process.argv)
40 42
43let log = getLogger(program[ 'verbose' ])
44
41getServerCredentials(command) 45getServerCredentials(command)
42 .then(({ url, username, password }) => { 46 .then(({ url, username, password }) => {
43 if (!program[ 'targetUrl' ]) { 47 if (!program[ 'targetUrl' ]) {
44 console.error('--targetUrl field is required.') 48 exitError('--target-url field is required.')
49 }
45 50
46 process.exit(-1) 51 try {
52 accessSync(program[ 'tmpdir' ], constants.R_OK | constants.W_OK)
53 } catch (e) {
54 exitError('--tmpdir %s: directory does not exist or is not accessible', program[ 'tmpdir' ])
47 } 55 }
48 56
49 removeEndSlashes(url) 57 removeEndSlashes(url)
@@ -53,8 +61,7 @@ getServerCredentials(command)
53 61
54 run(url, user) 62 run(url, user)
55 .catch(err => { 63 .catch(err => {
56 console.error(err) 64 exitError(err)
57 process.exit(-1)
58 }) 65 })
59 }) 66 })
60 67
@@ -68,30 +75,32 @@ async function run (url: string, user: UserInfo) {
68 const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] 75 const options = [ '-j', '--flat-playlist', '--playlist-reverse' ]
69 youtubeDL.getInfo(program[ 'targetUrl' ], options, processOptions, async (err, info) => { 76 youtubeDL.getInfo(program[ 'targetUrl' ], options, processOptions, async (err, info) => {
70 if (err) { 77 if (err) {
71 console.log(err.message) 78 exitError(err.message)
72 process.exit(1)
73 } 79 }
74 80
75 let infoArray: any[] 81 let infoArray: any[]
76 82
77 // Normalize utf8 fields 83 // Normalize utf8 fields
78 if (Array.isArray(info) === true) { 84 infoArray = [].concat(info);
79 infoArray = info.map(i => normalizeObject(i)) 85 if (program[ 'first' ]) {
80 } else { 86 infoArray = infoArray.slice(0, program[ 'first' ])
81 infoArray = [ normalizeObject(info) ] 87 } else if (program[ 'last' ]) {
88 infoArray = infoArray.slice(- program[ 'last' ])
82 } 89 }
83 console.log('Will download and upload %d videos.\n', infoArray.length) 90 infoArray = infoArray.map(i => normalizeObject(i))
91
92 log.info('Will download and upload %d videos.\n', infoArray.length)
84 93
85 for (const info of infoArray) { 94 for (const info of infoArray) {
86 await processVideo({ 95 await processVideo({
87 cwd: processOptions.cwd, 96 cwd: program[ 'tmpdir' ],
88 url, 97 url,
89 user, 98 user,
90 youtubeInfo: info 99 youtubeInfo: info
91 }) 100 })
92 } 101 }
93 102
94 console.log('Video/s for user %s imported: %s', program[ 'username' ], program[ 'targetUrl' ]) 103 log.info('Video/s for user %s imported: %s', user.username, program[ 'targetUrl' ])
95 process.exit(0) 104 process.exit(0)
96 }) 105 })
97} 106}
@@ -105,21 +114,21 @@ function processVideo (parameters: {
105 const { youtubeInfo, cwd, url, user } = parameters 114 const { youtubeInfo, cwd, url, user } = parameters
106 115
107 return new Promise(async res => { 116 return new Promise(async res => {
108 if (program[ 'verbose' ]) console.log('Fetching object.', youtubeInfo) 117 log.debug('Fetching object.', youtubeInfo)
109 118
110 const videoInfo = await fetchObject(youtubeInfo) 119 const videoInfo = await fetchObject(youtubeInfo)
111 if (program[ 'verbose' ]) console.log('Fetched object.', videoInfo) 120 log.debug('Fetched object.', videoInfo)
112 121
113 if (program[ 'since' ]) { 122 if (program[ 'since' ]) {
114 if (buildOriginallyPublishedAt(videoInfo).getTime() < program[ 'since' ].getTime()) { 123 if (buildOriginallyPublishedAt(videoInfo).getTime() < program[ 'since' ].getTime()) {
115 console.log('Video "%s" has been published before "%s", don\'t upload it.\n', 124 log.info('Video "%s" has been published before "%s", don\'t upload it.\n',
116 videoInfo.title, formatDate(program[ 'since' ])); 125 videoInfo.title, formatDate(program[ 'since' ]));
117 return res(); 126 return res();
118 } 127 }
119 } 128 }
120 if (program[ 'until' ]) { 129 if (program[ 'until' ]) {
121 if (buildOriginallyPublishedAt(videoInfo).getTime() > program[ 'until' ].getTime()) { 130 if (buildOriginallyPublishedAt(videoInfo).getTime() > program[ 'until' ].getTime()) {
122 console.log('Video "%s" has been published after "%s", don\'t upload it.\n', 131 log.info('Video "%s" has been published after "%s", don\'t upload it.\n',
123 videoInfo.title, formatDate(program[ 'until' ])); 132 videoInfo.title, formatDate(program[ 'until' ]));
124 return res(); 133 return res();
125 } 134 }
@@ -127,27 +136,27 @@ function processVideo (parameters: {
127 136
128 const result = await searchVideoWithSort(url, videoInfo.title, '-match') 137 const result = await searchVideoWithSort(url, videoInfo.title, '-match')
129 138
130 console.log('############################################################\n') 139 log.info('############################################################\n')
131 140
132 if (result.body.data.find(v => v.name === videoInfo.title)) { 141 if (result.body.data.find(v => v.name === videoInfo.title)) {
133 console.log('Video "%s" already exists, don\'t reupload it.\n', videoInfo.title) 142 log.info('Video "%s" already exists, don\'t reupload it.\n', videoInfo.title)
134 return res() 143 return res()
135 } 144 }
136 145
137 const path = join(cwd, sha256(videoInfo.url) + '.mp4') 146 const path = join(cwd, sha256(videoInfo.url) + '.mp4')
138 147
139 console.log('Downloading video "%s"...', videoInfo.title) 148 log.info('Downloading video "%s"...', videoInfo.title)
140 149
141 const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] 150 const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ]
142 try { 151 try {
143 const youtubeDL = await safeGetYoutubeDL() 152 const youtubeDL = await safeGetYoutubeDL()
144 youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => { 153 youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => {
145 if (err) { 154 if (err) {
146 console.error(err) 155 log.error(err)
147 return res() 156 return res()
148 } 157 }
149 158
150 console.log(output.join('\n')) 159 log.info(output.join('\n'))
151 await uploadVideoOnPeerTube({ 160 await uploadVideoOnPeerTube({
152 cwd, 161 cwd,
153 url, 162 url,
@@ -158,7 +167,7 @@ function processVideo (parameters: {
158 return res() 167 return res()
159 }) 168 })
160 } catch (err) { 169 } catch (err) {
161 console.log(err.message) 170 log.error(err.message)
162 return res() 171 return res()
163 } 172 }
164 }) 173 })
@@ -217,7 +226,7 @@ async function uploadVideoOnPeerTube (parameters: {
217 fixture: videoPath 226 fixture: videoPath
218 }) 227 })
219 228
220 console.log('\nUploading on PeerTube video "%s".', videoAttributes.name) 229 log.info('\nUploading on PeerTube video "%s".', videoAttributes.name)
221 230
222 let accessToken = await getAccessTokenOrDie(url, user) 231 let accessToken = await getAccessTokenOrDie(url, user)
223 232
@@ -225,21 +234,20 @@ async function uploadVideoOnPeerTube (parameters: {
225 await uploadVideo(url, accessToken, videoAttributes) 234 await uploadVideo(url, accessToken, videoAttributes)
226 } catch (err) { 235 } catch (err) {
227 if (err.message.indexOf('401') !== -1) { 236 if (err.message.indexOf('401') !== -1) {
228 console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.') 237 log.info('Got 401 Unauthorized, token may have expired, renewing token and retry.')
229 238
230 accessToken = await getAccessTokenOrDie(url, user) 239 accessToken = await getAccessTokenOrDie(url, user)
231 240
232 await uploadVideo(url, accessToken, videoAttributes) 241 await uploadVideo(url, accessToken, videoAttributes)
233 } else { 242 } else {
234 console.log(err.message) 243 exitError(err.message)
235 process.exit(1)
236 } 244 }
237 } 245 }
238 246
239 await remove(videoPath) 247 await remove(videoPath)
240 if (thumbnailfile) await remove(thumbnailfile) 248 if (thumbnailfile) await remove(thumbnailfile)
241 249
242 console.log('Uploaded video "%s"!\n', videoAttributes.name) 250 log.warn('Uploaded video "%s"!\n', videoAttributes.name)
243} 251}
244 252
245/* ---------------------------------------------------------- */ 253/* ---------------------------------------------------------- */
@@ -355,20 +363,17 @@ async function getAccessTokenOrDie (url: string, user: UserInfo) {
355 const res = await login(url, client, user) 363 const res = await login(url, client, user)
356 return res.body.access_token 364 return res.body.access_token
357 } catch (err) { 365 } catch (err) {
358 console.error('Cannot authenticate. Please check your username/password.') 366 exitError('Cannot authenticate. Please check your username/password.')
359 process.exit(-1)
360 } 367 }
361} 368}
362 369
363function parseDate (dateAsStr: string): Date { 370function parseDate (dateAsStr: string): Date {
364 if (!/\d{4}-\d{2}-\d{2}/.test(dateAsStr)) { 371 if (!/\d{4}-\d{2}-\d{2}/.test(dateAsStr)) {
365 console.error(`Invalid date passed: ${dateAsStr}. Expected format: YYYY-MM-DD. See help for usage.`); 372 exitError(`Invalid date passed: ${dateAsStr}. Expected format: YYYY-MM-DD. See help for usage.`);
366 process.exit(-1);
367 } 373 }
368 const date = new Date(dateAsStr); 374 const date = new Date(dateAsStr);
369 if (isNaN(date.getTime())) { 375 if (isNaN(date.getTime())) {
370 console.error(`Invalid date passed: ${dateAsStr}. See help for usage.`); 376 exitError(`Invalid date passed: ${dateAsStr}. See help for usage.`);
371 process.exit(-1);
372 } 377 }
373 return date; 378 return date;
374} 379}
@@ -376,3 +381,9 @@ function parseDate (dateAsStr: string): Date {
376function formatDate (date: Date): string { 381function formatDate (date: Date): string {
377 return date.toISOString().split('T')[0]; 382 return date.toISOString().split('T')[0];
378} 383}
384
385function exitError (message:string, ...meta: any[]) {
386 // use console.error instead of log.error here
387 console.error(message, ...meta)
388 process.exit(-1)
389}
diff --git a/server/typings/activitypub-processor.model.ts b/server/typings/activitypub-processor.model.ts
index 37b2859de..7ed3a65b1 100644
--- a/server/typings/activitypub-processor.model.ts
+++ b/server/typings/activitypub-processor.model.ts
@@ -1,10 +1,9 @@
1import { Activity } from '../../shared/models/activitypub' 1import { Activity } from '../../shared/models/activitypub'
2import { ActorModel } from '../models/activitypub/actor' 2import { MActorDefault, MActorSignature } from './models'
3import { SignatureActorModel } from './models'
4 3
5export type APProcessorOptions<T extends Activity> = { 4export type APProcessorOptions<T extends Activity> = {
6 activity: T 5 activity: T
7 byActor: SignatureActorModel 6 byActor: MActorSignature
8 inboxActor?: ActorModel 7 inboxActor?: MActorDefault
9 fromFetch?: boolean 8 fromFetch?: boolean
10} 9}
diff --git a/server/typings/express.ts b/server/typings/express.ts
index f7da55ab0..3cc7c7632 100644
--- a/server/typings/express.ts
+++ b/server/typings/express.ts
@@ -1,89 +1,103 @@
1import { VideoChannelModel } from '../models/video/video-channel'
2import { VideoPlaylistModel } from '../models/video/video-playlist'
3import { VideoPlaylistElementModel } from '../models/video/video-playlist-element'
4import { UserModel } from '../models/account/user'
5import { VideoModel } from '../models/video/video'
6import { AccountModel } from '../models/account/account'
7import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership'
8import { ActorModel } from '../models/activitypub/actor'
9import { VideoCommentModel } from '../models/video/video-comment'
10import { VideoShareModel } from '../models/video/video-share'
11import { AccountVideoRateModel } from '../models/account/account-video-rate'
12import { ActorFollowModel } from '../models/activitypub/actor-follow'
13import { ServerModel } from '../models/server/server'
14import { VideoFileModel } from '../models/video/video-file'
15import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
16import { ServerBlocklistModel } from '../models/server/server-blocklist'
17import { AccountBlocklistModel } from '../models/account/account-blocklist'
18import { VideoImportModel } from '../models/video/video-import'
19import { VideoAbuseModel } from '../models/video/video-abuse'
20import { VideoBlacklistModel } from '../models/video/video-blacklist'
21import { VideoCaptionModel } from '../models/video/video-caption'
22import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
23import { RegisteredPlugin } from '../lib/plugins/plugin-manager' 1import { RegisteredPlugin } from '../lib/plugins/plugin-manager'
24import { PluginModel } from '../models/server/plugin' 2import {
25import { SignatureActorModel } from './models' 3 MAccountDefault,
4 MActorAccountChannelId,
5 MActorFollowActorsDefault,
6 MActorFollowActorsDefaultSubscription,
7 MActorFull,
8 MChannelAccountDefault,
9 MComment,
10 MCommentOwnerVideoReply,
11 MUserDefault,
12 MVideoAbuse,
13 MVideoBlacklist,
14 MVideoCaptionVideo,
15 MVideoFullLight,
16 MVideoIdThumbnail,
17 MVideoRedundancyVideo,
18 MVideoShareActor,
19 MVideoThumbnail,
20 MVideoWithRights
21} from './models'
22import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist'
23import { MVideoImportDefault } from '@server/typings/models/video/video-import'
24import { MAccountBlocklist, MStreamingPlaylist, MVideoFile } from '@server/typings/models'
25import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element'
26import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate'
27import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership'
28import { MPlugin, MServer } from '@server/typings/models/server'
29import { MServerBlocklist } from './models/server/server-blocklist'
30import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
26 31
27declare module 'express' { 32declare module 'express' {
28 33
29 interface Response { 34 interface Response {
35
30 locals: { 36 locals: {
31 video?: VideoModel 37 videoAll?: MVideoFullLight
32 videoShare?: VideoShareModel 38 onlyVideo?: MVideoThumbnail
33 videoFile?: VideoFileModel 39 onlyVideoWithRights?: MVideoWithRights
40 videoId?: MVideoIdThumbnail
41
42 videoShare?: MVideoShareActor
43
44 videoFile?: MVideoFile
45
46 videoImport?: MVideoImportDefault
47
48 videoBlacklist?: MVideoBlacklist
49
50 videoCaption?: MVideoCaptionVideo
51
52 videoAbuse?: MVideoAbuse
34 53
35 videoImport?: VideoImportModel 54 videoStreamingPlaylist?: MStreamingPlaylist
36 55
37 videoBlacklist?: VideoBlacklistModel 56 videoChannel?: MChannelAccountDefault
38 57
39 videoCaption?: VideoCaptionModel 58 videoPlaylistFull?: MVideoPlaylistFull
59 videoPlaylistSummary?: MVideoPlaylistFullSummary
40 60
41 videoAbuse?: VideoAbuseModel 61 videoPlaylistElement?: MVideoPlaylistElement
62 videoPlaylistElementAP?: MVideoPlaylistElementVideoUrlPlaylistPrivacy
42 63
43 videoStreamingPlaylist?: VideoStreamingPlaylistModel 64 accountVideoRate?: MAccountVideoRateAccountVideo
44 65
45 videoChannel?: VideoChannelModel 66 videoCommentFull?: MCommentOwnerVideoReply
67 videoCommentThread?: MComment
46 68
47 videoPlaylist?: VideoPlaylistModel 69 follow?: MActorFollowActorsDefault
48 videoPlaylistElement?: VideoPlaylistElementModel 70 subscription?: MActorFollowActorsDefaultSubscription
49 71
50 accountVideoRate?: AccountVideoRateModel 72 nextOwner?: MAccountDefault
73 videoChangeOwnership?: MVideoChangeOwnershipFull
51 74
52 videoComment?: VideoCommentModel 75 account?: MAccountDefault
53 videoCommentThread?: VideoCommentModel
54 76
55 follow?: ActorFollowModel 77 actorFull?: MActorFull
56 subscription?: ActorFollowModel
57 78
58 nextOwner?: AccountModel 79 user?: MUserDefault
59 videoChangeOwnership?: VideoChangeOwnershipModel
60 account?: AccountModel
61 actor?: ActorModel
62 user?: UserModel
63 80
64 server?: ServerModel 81 server?: MServer
65 82
66 videoRedundancy?: VideoRedundancyModel 83 videoRedundancy?: MVideoRedundancyVideo
67 84
68 accountBlock?: AccountBlocklistModel 85 accountBlock?: MAccountBlocklist
69 serverBlock?: ServerBlocklistModel 86 serverBlock?: MServerBlocklist
70 87
71 oauth?: { 88 oauth?: {
72 token: { 89 token: MOAuthTokenUser
73 User: UserModel
74 user: UserModel
75 }
76 } 90 }
77 91
78 signature?: { 92 signature?: {
79 actor: SignatureActorModel 93 actor: MActorAccountChannelId
80 } 94 }
81 95
82 authenticated?: boolean 96 authenticated?: boolean
83 97
84 registeredPlugin?: RegisteredPlugin 98 registeredPlugin?: RegisteredPlugin
85 99
86 plugin?: PluginModel 100 plugin?: MPlugin
87 } 101 }
88 } 102 }
89} 103}
diff --git a/server/typings/models/account/account-blocklist.ts b/server/typings/models/account/account-blocklist.ts
new file mode 100644
index 000000000..c9cb55332
--- /dev/null
+++ b/server/typings/models/account/account-blocklist.ts
@@ -0,0 +1,25 @@
1import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
2import { PickWith } from '../../utils'
3import { MAccountDefault, MAccountFormattable } from './account'
4
5type Use<K extends keyof AccountBlocklistModel, M> = PickWith<AccountBlocklistModel, K, M>
6
7// ############################################################################
8
9export type MAccountBlocklist = Omit<AccountBlocklistModel, 'ByAccount' | 'BlockedAccount'>
10
11// ############################################################################
12
13export type MAccountBlocklistId = Pick<AccountBlocklistModel, 'id'>
14
15export type MAccountBlocklistAccounts = MAccountBlocklist &
16 Use<'ByAccount', MAccountDefault> &
17 Use<'BlockedAccount', MAccountDefault>
18
19// ############################################################################
20
21// Format for API or AP object
22
23export type MAccountBlocklistFormattable = Pick<MAccountBlocklist, 'createdAt'> &
24 Use<'ByAccount', MAccountFormattable> &
25 Use<'BlockedAccount', MAccountFormattable>
diff --git a/server/typings/models/account/account.ts b/server/typings/models/account/account.ts
new file mode 100644
index 000000000..ec78fece8
--- /dev/null
+++ b/server/typings/models/account/account.ts
@@ -0,0 +1,95 @@
1import { AccountModel } from '../../../models/account/account'
2import {
3 MActor,
4 MActorAP,
5 MActorAPI,
6 MActorAudience,
7 MActorDefault,
8 MActorDefaultLight,
9 MActorFormattable,
10 MActorId,
11 MActorServer,
12 MActorSummary,
13 MActorSummaryFormattable,
14 MActorUrl
15} from './actor'
16import { FunctionProperties, PickWith } from '../../utils'
17import { MAccountBlocklistId } from './account-blocklist'
18import { MChannelDefault } from '@server/typings/models'
19
20type Use<K extends keyof AccountModel, M> = PickWith<AccountModel, K, M>
21
22// ############################################################################
23
24export type MAccount = Omit<AccountModel, 'Actor' | 'User' | 'Application' | 'VideoChannels' | 'VideoPlaylists' |
25 'VideoComments' | 'BlockedAccounts'>
26
27// ############################################################################
28
29// Only some attributes
30export type MAccountId = Pick<MAccount, 'id'>
31export type MAccountUserId = Pick<MAccount, 'userId'>
32
33// Only some Actor attributes
34export type MAccountUrl = Use<'Actor', MActorUrl>
35export type MAccountAudience = Use<'Actor', MActorAudience>
36
37export type MAccountIdActor = MAccountId &
38 Use<'Actor', MActor>
39
40export type MAccountIdActorId = MAccountId &
41 Use<'Actor', MActorId>
42
43// ############################################################################
44
45// Default scope
46export type MAccountDefault = MAccount &
47 Use<'Actor', MActorDefault>
48
49// Default with default association scopes
50export type MAccountDefaultChannelDefault = MAccount &
51 Use<'Actor', MActorDefault> &
52 Use<'VideoChannels', MChannelDefault[]>
53
54// We don't need some actors attributes
55export type MAccountLight = MAccount &
56 Use<'Actor', MActorDefaultLight>
57
58// ############################################################################
59
60// Full actor
61export type MAccountActor = MAccount &
62 Use<'Actor', MActor>
63
64// Full actor with server
65export type MAccountServer = MAccount &
66 Use<'Actor', MActorServer>
67
68// ############################################################################
69
70// For API
71
72export type MAccountSummary = FunctionProperties<MAccount> &
73 Pick<MAccount, 'id' | 'name'> &
74 Use<'Actor', MActorSummary>
75
76export type MAccountSummaryBlocks = MAccountSummary &
77 Use<'BlockedAccounts', MAccountBlocklistId[]>
78
79export type MAccountAPI = MAccount &
80 Use<'Actor', MActorAPI>
81
82// ############################################################################
83
84// Format for API or AP object
85
86export type MAccountSummaryFormattable = FunctionProperties<MAccount> &
87 Pick<MAccount, 'id' | 'name'> &
88 Use<'Actor', MActorSummaryFormattable>
89
90export type MAccountFormattable = FunctionProperties<MAccount> &
91 Pick<MAccount, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'userId'> &
92 Use<'Actor', MActorFormattable>
93
94export type MAccountAP = Pick<MAccount, 'name' | 'description'> &
95 Use<'Actor', MActorAP>
diff --git a/server/typings/models/account/actor-follow.ts b/server/typings/models/account/actor-follow.ts
new file mode 100644
index 000000000..17a47b8df
--- /dev/null
+++ b/server/typings/models/account/actor-follow.ts
@@ -0,0 +1,67 @@
1import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
2import {
3 MActor,
4 MActorAccount,
5 MActorAccountChannel,
6 MActorChannelAccountActor,
7 MActorDefault,
8 MActorFormattable,
9 MActorHost,
10 MActorUsername
11} from './actor'
12import { PickWith } from '../../utils'
13import { ActorModel } from '@server/models/activitypub/actor'
14import { MChannelDefault } from '@server/typings/models'
15
16type Use<K extends keyof ActorFollowModel, M> = PickWith<ActorFollowModel, K, M>
17
18// ############################################################################
19
20export type MActorFollow = Omit<ActorFollowModel, 'ActorFollower' | 'ActorFollowing'>
21
22// ############################################################################
23
24export type MActorFollowFollowingHost = MActorFollow &
25 Use<'ActorFollowing', MActorUsername & MActorHost>
26
27// ############################################################################
28
29// With actors or actors default
30
31export type MActorFollowActors = MActorFollow &
32 Use<'ActorFollower', MActor> &
33 Use<'ActorFollowing', MActor>
34
35export type MActorFollowActorsDefault = MActorFollow &
36 Use<'ActorFollower', MActorDefault> &
37 Use<'ActorFollowing', MActorDefault>
38
39export type MActorFollowFull = MActorFollow &
40 Use<'ActorFollower', MActorAccountChannel> &
41 Use<'ActorFollowing', MActorAccountChannel>
42
43// ############################################################################
44
45// For subscriptions
46
47type SubscriptionFollowing = MActorDefault &
48 PickWith<ActorModel, 'VideoChannel', MChannelDefault>
49
50export type MActorFollowActorsDefaultSubscription = MActorFollow &
51 Use<'ActorFollower', MActorDefault> &
52 Use<'ActorFollowing', SubscriptionFollowing>
53
54export type MActorFollowFollowingFullFollowerAccount = MActorFollow &
55 Use<'ActorFollower', MActorAccount> &
56 Use<'ActorFollowing', MActorAccountChannel>
57
58export type MActorFollowSubscriptions = MActorFollow &
59 Use<'ActorFollowing', MActorChannelAccountActor>
60
61// ############################################################################
62
63// Format for API or AP object
64
65export type MActorFollowFormattable = Pick<MActorFollow, 'id' | 'score' | 'state' | 'createdAt' | 'updatedAt'> &
66 Use<'ActorFollower', MActorFormattable> &
67 Use<'ActorFollowing', MActorFormattable>
diff --git a/server/typings/models/account/actor.ts b/server/typings/models/account/actor.ts
new file mode 100644
index 000000000..d4bcac4a3
--- /dev/null
+++ b/server/typings/models/account/actor.ts
@@ -0,0 +1,121 @@
1import { ActorModel } from '../../../models/activitypub/actor'
2import { FunctionProperties, PickWith, PickWithOpt } from '../../utils'
3import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from './account'
4import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server'
5import { MAvatar, MAvatarFormattable } from './avatar'
6import { MChannel, MChannelAccountActor, MChannelAccountDefault, MChannelId, MChannelIdActor } from '../video'
7
8type Use<K extends keyof ActorModel, M> = PickWith<ActorModel, K, M>
9
10// ############################################################################
11
12export type MActor = Omit<ActorModel, 'Account' | 'VideoChannel' | 'ActorFollowing' | 'Avatar' | 'ActorFollowers' | 'Server'>
13
14// ############################################################################
15
16export type MActorUrl = Pick<MActor, 'url'>
17export type MActorId = Pick<MActor, 'id'>
18export type MActorUsername = Pick<MActor, 'preferredUsername'>
19
20export type MActorFollowersUrl = Pick<MActor, 'followersUrl'>
21export type MActorAudience = MActorUrl & MActorFollowersUrl
22export type MActorFollowerException = Pick<ActorModel, 'sharedInboxUrl' | 'inboxUrl'>
23export type MActorSignature = MActorAccountChannelId
24
25export type MActorLight = Omit<MActor, 'privateKey' | 'privateKey'>
26
27// ############################################################################
28
29// Some association attributes
30
31export type MActorHost = Use<'Server', MServerHost>
32export type MActorRedundancyAllowedOpt = PickWithOpt<ActorModel, 'Server', MServerRedundancyAllowed>
33
34export type MActorDefaultLight = MActorLight &
35 Use<'Server', MServerHost> &
36 Use<'Avatar', MAvatar>
37
38export type MActorAccountId = MActor &
39 Use<'Account', MAccountId>
40export type MActorAccountIdActor = MActor &
41 Use<'Account', MAccountIdActor>
42
43export type MActorChannelId = MActor &
44 Use<'VideoChannel', MChannelId>
45export type MActorChannelIdActor = MActor &
46 Use<'VideoChannel', MChannelIdActor>
47
48export type MActorAccountChannelId = MActorAccountId & MActorChannelId
49export type MActorAccountChannelIdActor = MActorAccountIdActor & MActorChannelIdActor
50
51// ############################################################################
52
53// Include raw account/channel/server
54
55export type MActorAccount = MActor &
56 Use<'Account', MAccount>
57
58export type MActorChannel = MActor &
59 Use<'VideoChannel', MChannel>
60
61export type MActorAccountChannel = MActorAccount & MActorChannel
62
63export type MActorServer = MActor &
64 Use<'Server', MServer>
65
66// ############################################################################
67
68// Complex actor associations
69
70export type MActorDefault = MActor &
71 Use<'Server', MServer> &
72 Use<'Avatar', MAvatar>
73
74// Actor with channel that is associated to an account and its actor
75// Actor -> VideoChannel -> Account -> Actor
76export type MActorChannelAccountActor = MActor &
77 Use<'VideoChannel', MChannelAccountActor>
78
79export type MActorFull = MActor &
80 Use<'Server', MServer> &
81 Use<'Avatar', MAvatar> &
82 Use<'Account', MAccount> &
83 Use<'VideoChannel', MChannelAccountActor>
84
85// Same than ActorFull, but the account and the channel have their actor
86export type MActorFullActor = MActor &
87 Use<'Server', MServer> &
88 Use<'Avatar', MAvatar> &
89 Use<'Account', MAccountDefault> &
90 Use<'VideoChannel', MChannelAccountDefault>
91
92// ############################################################################
93
94// API
95
96export type MActorSummary = FunctionProperties<MActor> &
97 Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> &
98 Use<'Server', MServerHost> &
99 Use<'Avatar', MAvatar>
100
101export type MActorSummaryBlocks = MActorSummary &
102 Use<'Server', MServerHostBlocks>
103
104export type MActorAPI = Omit<MActorDefault, 'publicKey' | 'privateKey' | 'inboxUrl' | 'outboxUrl' | 'sharedInboxUrl' |
105 'followersUrl' | 'followingUrl' | 'url' | 'createdAt' | 'updatedAt'>
106
107// ############################################################################
108
109// Format for API or AP object
110
111export type MActorSummaryFormattable = FunctionProperties<MActor> &
112 Pick<MActor, 'url' | 'preferredUsername'> &
113 Use<'Server', MServerHost> &
114 Use<'Avatar', MAvatarFormattable>
115
116export type MActorFormattable = MActorSummaryFormattable &
117 Pick<MActor, 'id' | 'followingCount' | 'followersCount' | 'createdAt' | 'updatedAt'> &
118 Use<'Server', MServerHost & Partial<Pick<MServer, 'redundancyAllowed'>>>
119
120export type MActorAP = MActor &
121 Use<'Avatar', MAvatar>
diff --git a/server/typings/models/account/avatar.ts b/server/typings/models/account/avatar.ts
new file mode 100644
index 000000000..8af6cc787
--- /dev/null
+++ b/server/typings/models/account/avatar.ts
@@ -0,0 +1,11 @@
1import { AvatarModel } from '../../../models/avatar/avatar'
2import { FunctionProperties } from '@server/typings/utils'
3
4export type MAvatar = AvatarModel
5
6// ############################################################################
7
8// Format for API or AP object
9
10export type MAvatarFormattable = FunctionProperties<MAvatar> &
11 Pick<MAvatar, 'filename' | 'createdAt' | 'updatedAt'>
diff --git a/server/typings/models/account/index.d.ts b/server/typings/models/account/index.d.ts
new file mode 100644
index 000000000..513c09c40
--- /dev/null
+++ b/server/typings/models/account/index.d.ts
@@ -0,0 +1,5 @@
1export * from './account'
2export * from './account-blocklist'
3export * from './actor'
4export * from './actor-follow'
5export * from './avatar'
diff --git a/server/typings/models/actor-follow.ts b/server/typings/models/actor-follow.ts
deleted file mode 100644
index 952ef877b..000000000
--- a/server/typings/models/actor-follow.ts
+++ /dev/null
@@ -1,8 +0,0 @@
1import { ActorFollowModel } from '../../models/activitypub/actor-follow'
2import { ActorModelOnly } from './actor'
3
4export type ActorFollowModelOnly = Omit<ActorFollowModel, 'ActorFollower' | 'ActorFollowing'>
5export type ActorFollowModelLight = ActorFollowModelOnly & {
6 ActorFollower: ActorModelOnly
7 ActorFollowing: ActorModelOnly
8}
diff --git a/server/typings/models/actor.ts b/server/typings/models/actor.ts
deleted file mode 100644
index 2656c7b66..000000000
--- a/server/typings/models/actor.ts
+++ /dev/null
@@ -1,22 +0,0 @@
1import { ActorModel } from '../../models/activitypub/actor'
2import { VideoChannelModel } from '../../models/video/video-channel'
3import { AccountModel } from '../../models/account/account'
4import { FunctionProperties } from '../utils'
5
6export type VideoChannelModelId = FunctionProperties<VideoChannelModel>
7export type AccountModelId = FunctionProperties<AccountModel> | Pick<AccountModel, 'id'>
8
9export type VideoChannelModelIdActor = VideoChannelModelId & Pick<VideoChannelModel, 'Actor'>
10export type AccountModelIdActor = AccountModelId & Pick<AccountModel, 'Actor'>
11
12export type ActorModelUrl = Pick<ActorModel, 'url'>
13export type ActorModelOnly = Omit<ActorModel, 'Account' | 'VideoChannel' | 'ActorFollowing' | 'Avatar' | 'ActorFollowers' | 'Server'>
14export type ActorModelId = Pick<ActorModelOnly, 'id'>
15
16export type SignatureActorModel = ActorModelOnly & {
17 VideoChannel: VideoChannelModelIdActor
18
19 Account: AccountModelIdActor
20}
21
22export type ActorFollowerException = Pick<ActorModel, 'sharedInboxUrl' | 'inboxUrl'>
diff --git a/server/typings/models/index.d.ts b/server/typings/models/index.d.ts
index c90656965..78b4948ce 100644
--- a/server/typings/models/index.d.ts
+++ b/server/typings/models/index.d.ts
@@ -1 +1,5 @@
1export * from './actor' 1export * from './account'
2export * from './oauth'
3export * from './server'
4export * from './user'
5export * from './video'
diff --git a/server/typings/models/oauth/index.d.ts b/server/typings/models/oauth/index.d.ts
new file mode 100644
index 000000000..36b7ea8ca
--- /dev/null
+++ b/server/typings/models/oauth/index.d.ts
@@ -0,0 +1,2 @@
1export * from './oauth-client'
2export * from './oauth-token'
diff --git a/server/typings/models/oauth/oauth-client.ts b/server/typings/models/oauth/oauth-client.ts
new file mode 100644
index 000000000..904a07863
--- /dev/null
+++ b/server/typings/models/oauth/oauth-client.ts
@@ -0,0 +1,3 @@
1import { OAuthClientModel } from '@server/models/oauth/oauth-client'
2
3export type MOAuthClient = Omit<OAuthClientModel, 'OAuthTokens'>
diff --git a/server/typings/models/oauth/oauth-token.ts b/server/typings/models/oauth/oauth-token.ts
new file mode 100644
index 000000000..af3412925
--- /dev/null
+++ b/server/typings/models/oauth/oauth-token.ts
@@ -0,0 +1,13 @@
1import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
2import { PickWith } from '@server/typings/utils'
3import { MUserAccountUrl } from '@server/typings/models'
4
5type Use<K extends keyof OAuthTokenModel, M> = PickWith<OAuthTokenModel, K, M>
6
7// ############################################################################
8
9export type MOAuthToken = Omit<OAuthTokenModel, 'User' | 'OAuthClients'>
10
11export type MOAuthTokenUser = MOAuthToken &
12 Use<'User', MUserAccountUrl> &
13 { user?: MUserAccountUrl }
diff --git a/server/typings/models/server/index.d.ts b/server/typings/models/server/index.d.ts
new file mode 100644
index 000000000..c853795ad
--- /dev/null
+++ b/server/typings/models/server/index.d.ts
@@ -0,0 +1,3 @@
1export * from './plugin'
2export * from './server'
3export * from './server-blocklist'
diff --git a/server/typings/models/server/plugin.ts b/server/typings/models/server/plugin.ts
new file mode 100644
index 000000000..94674c318
--- /dev/null
+++ b/server/typings/models/server/plugin.ts
@@ -0,0 +1,10 @@
1import { PluginModel } from '@server/models/server/plugin'
2
3export type MPlugin = PluginModel
4
5// ############################################################################
6
7// Format for API or AP object
8
9export type MPluginFormattable = Pick<MPlugin, 'name' | 'type' | 'version' | 'latestVersion' | 'enabled' | 'uninstalled'
10 | 'peertubeEngine' | 'description' | 'homepage' | 'settings' | 'createdAt' | 'updatedAt'>
diff --git a/server/typings/models/server/server-blocklist.ts b/server/typings/models/server/server-blocklist.ts
new file mode 100644
index 000000000..c81f604f5
--- /dev/null
+++ b/server/typings/models/server/server-blocklist.ts
@@ -0,0 +1,23 @@
1import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
2import { PickWith } from '@server/typings/utils'
3import { MAccountDefault, MAccountFormattable, MServer, MServerFormattable } from '@server/typings/models'
4
5type Use<K extends keyof ServerBlocklistModel, M> = PickWith<ServerBlocklistModel, K, M>
6
7// ############################################################################
8
9export type MServerBlocklist = Omit<ServerBlocklistModel, 'ByAccount' | 'BlockedServer'>
10
11// ############################################################################
12
13export type MServerBlocklistAccountServer = MServerBlocklist &
14 Use<'ByAccount', MAccountDefault> &
15 Use<'BlockedServer', MServer>
16
17// ############################################################################
18
19// Format for API or AP object
20
21export type MServerBlocklistFormattable = Pick<MServerBlocklist, 'createdAt'> &
22 Use<'ByAccount', MAccountFormattable> &
23 Use<'BlockedServer', MServerFormattable>
diff --git a/server/typings/models/server/server.ts b/server/typings/models/server/server.ts
new file mode 100644
index 000000000..190cc0c28
--- /dev/null
+++ b/server/typings/models/server/server.ts
@@ -0,0 +1,24 @@
1import { ServerModel } from '../../../models/server/server'
2import { FunctionProperties, PickWith } from '../../utils'
3import { MAccountBlocklistId } from '../account'
4
5type Use<K extends keyof ServerModel, M> = PickWith<ServerModel, K, M>
6
7// ############################################################################
8
9export type MServer = Omit<ServerModel, 'Actors' | 'BlockedByAccounts'>
10
11// ############################################################################
12
13export type MServerHost = Pick<MServer, 'host'>
14export type MServerRedundancyAllowed = Pick<MServer, 'redundancyAllowed'>
15
16export type MServerHostBlocks = MServerHost &
17 Use<'BlockedByAccounts', MAccountBlocklistId[]>
18
19// ############################################################################
20
21// Format for API or AP object
22
23export type MServerFormattable = FunctionProperties<MServer> &
24 Pick<MServer, 'host'>
diff --git a/server/typings/models/user/index.d.ts b/server/typings/models/user/index.d.ts
new file mode 100644
index 000000000..6657b2128
--- /dev/null
+++ b/server/typings/models/user/index.d.ts
@@ -0,0 +1,4 @@
1export * from './user'
2export * from './user-notification'
3export * from './user-notification-setting'
4export * from './user-video-history'
diff --git a/server/typings/models/user/user-notification-setting.ts b/server/typings/models/user/user-notification-setting.ts
new file mode 100644
index 000000000..c674add1b
--- /dev/null
+++ b/server/typings/models/user/user-notification-setting.ts
@@ -0,0 +1,9 @@
1import { UserNotificationSettingModel } from '@server/models/account/user-notification-setting'
2
3export type MNotificationSetting = Omit<UserNotificationSettingModel, 'User'>
4
5// ############################################################################
6
7// Format for API or AP object
8
9export type MNotificationSettingFormattable = MNotificationSetting
diff --git a/server/typings/models/user/user-notification.ts b/server/typings/models/user/user-notification.ts
new file mode 100644
index 000000000..f9daf5eb2
--- /dev/null
+++ b/server/typings/models/user/user-notification.ts
@@ -0,0 +1,77 @@
1import { UserNotificationModel } from '../../../models/account/user-notification'
2import { PickWith } from '../../utils'
3import { VideoModel } from '../../../models/video/video'
4import { ActorModel } from '../../../models/activitypub/actor'
5import { ServerModel } from '../../../models/server/server'
6import { AvatarModel } from '../../../models/avatar/avatar'
7import { VideoChannelModel } from '../../../models/video/video-channel'
8import { AccountModel } from '../../../models/account/account'
9import { VideoCommentModel } from '../../../models/video/video-comment'
10import { VideoAbuseModel } from '../../../models/video/video-abuse'
11import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
12import { VideoImportModel } from '../../../models/video/video-import'
13import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
14
15type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationModel, K, M>
16
17// ############################################################################
18
19export namespace UserNotificationIncludes {
20 export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name'>
21 export type VideoIncludeChannel = VideoInclude &
22 PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor>
23
24 export type ActorInclude = Pick<ActorModel, 'preferredUsername' | 'getHost'> &
25 PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> &
26 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>>
27
28 export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'>
29 export type VideoChannelIncludeActor = VideoChannelInclude &
30 PickWith<VideoChannelModel, 'Actor', ActorInclude>
31
32 export type AccountInclude = Pick<AccountModel, 'id' | 'name' | 'getDisplayName'>
33 export type AccountIncludeActor = AccountInclude &
34 PickWith<AccountModel, 'Actor', ActorInclude>
35
36 export type VideoCommentInclude = Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> &
37 PickWith<VideoCommentModel, 'Account', AccountIncludeActor> &
38 PickWith<VideoCommentModel, 'Video', VideoInclude>
39
40 export type VideoAbuseInclude = Pick<VideoAbuseModel, 'id'> &
41 PickWith<VideoAbuseModel, 'Video', VideoInclude>
42
43 export type VideoBlacklistInclude = Pick<VideoBlacklistModel, 'id'> &
44 PickWith<VideoAbuseModel, 'Video', VideoInclude>
45
46 export type VideoImportInclude = Pick<VideoImportModel, 'id' | 'magnetUri' | 'targetUrl' | 'torrentName'> &
47 PickWith<VideoImportModel, 'Video', VideoInclude>
48
49 export type ActorFollower = Pick<ActorModel, 'preferredUsername' | 'getHost'> &
50 PickWith<ActorModel, 'Account', AccountInclude> &
51 PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> &
52 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>>
53
54 export type ActorFollowing = Pick<ActorModel, 'preferredUsername'> &
55 PickWith<ActorModel, 'VideoChannel', VideoChannelInclude> &
56 PickWith<ActorModel, 'Account', AccountInclude>
57
58 export type ActorFollowInclude = Pick<ActorFollowModel, 'id' | 'state'> &
59 PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> &
60 PickWith<ActorFollowModel, 'ActorFollowing', ActorFollowing>
61}
62
63// ############################################################################
64
65export type MUserNotification = Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'VideoAbuse' | 'VideoBlacklist' |
66 'VideoImport' | 'Account' | 'ActorFollow'>
67
68// ############################################################################
69
70export type UserNotificationModelForApi = MUserNotification &
71 Use<'Video', UserNotificationIncludes.VideoIncludeChannel> &
72 Use<'Comment', UserNotificationIncludes.VideoCommentInclude> &
73 Use<'VideoAbuse', UserNotificationIncludes.VideoAbuseInclude> &
74 Use<'VideoBlacklist', UserNotificationIncludes.VideoBlacklistInclude> &
75 Use<'VideoImport', UserNotificationIncludes.VideoImportInclude> &
76 Use<'ActorFollow', UserNotificationIncludes.ActorFollowInclude> &
77 Use<'Account', UserNotificationIncludes.AccountIncludeActor>
diff --git a/server/typings/models/user/user-video-history.ts b/server/typings/models/user/user-video-history.ts
new file mode 100644
index 000000000..62673ab1b
--- /dev/null
+++ b/server/typings/models/user/user-video-history.ts
@@ -0,0 +1,5 @@
1import { UserVideoHistoryModel } from '../../../models/account/user-video-history'
2
3export type MUserVideoHistory = Omit<UserVideoHistoryModel, 'Video' | 'User'>
4
5export type MUserVideoHistoryTime = Pick<MUserVideoHistory, 'currentTime'>
diff --git a/server/typings/models/user/user.ts b/server/typings/models/user/user.ts
new file mode 100644
index 000000000..52d6d4a05
--- /dev/null
+++ b/server/typings/models/user/user.ts
@@ -0,0 +1,70 @@
1import { UserModel } from '../../../models/account/user'
2import { PickWith, PickWithOpt } from '../../utils'
3import {
4 MAccount,
5 MAccountDefault,
6 MAccountDefaultChannelDefault,
7 MAccountFormattable,
8 MAccountId,
9 MAccountIdActorId,
10 MAccountUrl
11} from '../account'
12import { MNotificationSetting, MNotificationSettingFormattable } from './user-notification-setting'
13import { AccountModel } from '@server/models/account/account'
14import { MChannelFormattable } from '@server/typings/models'
15
16type Use<K extends keyof UserModel, M> = PickWith<UserModel, K, M>
17
18// ############################################################################
19
20export type MUser = Omit<UserModel, 'Account' | 'NotificationSetting' | 'VideoImports' | 'OAuthTokens'>
21
22// ############################################################################
23
24export type MUserQuotaUsed = MUser & { videoQuotaUsed?: number, videoQuotaUsedDaily?: number }
25export type MUserId = Pick<UserModel, 'id'>
26
27// ############################################################################
28
29// With account
30
31export type MUserAccountId = MUser &
32 Use<'Account', MAccountId>
33
34export type MUserAccountUrl = MUser &
35 Use<'Account', MAccountUrl & MAccountIdActorId>
36
37export type MUserAccount = MUser &
38 Use<'Account', MAccount>
39
40export type MUserAccountDefault = MUser &
41 Use<'Account', MAccountDefault>
42
43// With channel
44
45export type MUserNotifSettingChannelDefault = MUser &
46 Use<'NotificationSetting', MNotificationSetting> &
47 Use<'Account', MAccountDefaultChannelDefault>
48
49// With notification settings
50
51export type MUserWithNotificationSetting = MUser &
52 Use<'NotificationSetting', MNotificationSetting>
53
54export type MUserNotifSettingAccount = MUser &
55 Use<'NotificationSetting', MNotificationSetting> &
56 Use<'Account', MAccount>
57
58// Default scope
59
60export type MUserDefault = MUser &
61 Use<'NotificationSetting', MNotificationSetting> &
62 Use<'Account', MAccountDefault>
63
64// ############################################################################
65
66// Format for API or AP object
67
68export type MUserFormattable = MUserQuotaUsed &
69 Use<'Account', MAccountFormattable & PickWithOpt<AccountModel, 'VideoChannels', MChannelFormattable[]>> &
70 PickWithOpt<UserModel, 'NotificationSetting', MNotificationSettingFormattable>
diff --git a/server/typings/models/video-share.ts b/server/typings/models/video-share.ts
deleted file mode 100644
index 1406749d2..000000000
--- a/server/typings/models/video-share.ts
+++ /dev/null
@@ -1,3 +0,0 @@
1import { VideoShareModel } from '../../models/video/video-share'
2
3export type VideoShareModelOnly = Omit<VideoShareModel, 'Actor' | 'Video'>
diff --git a/server/typings/models/video/index.d.ts b/server/typings/models/video/index.d.ts
new file mode 100644
index 000000000..bd69c8a4b
--- /dev/null
+++ b/server/typings/models/video/index.d.ts
@@ -0,0 +1,18 @@
1export * from './schedule-video-update'
2export * from './tag'
3export * from './thumbnail'
4export * from './video'
5export * from './video-abuse'
6export * from './video-blacklist'
7export * from './video-caption'
8export * from './video-change-ownership'
9export * from './video-channels'
10export * from './video-comment'
11export * from './video-file'
12export * from './video-import'
13export * from './video-playlist'
14export * from './video-playlist-element'
15export * from './video-rate'
16export * from './video-redundancy'
17export * from './video-share'
18export * from './video-streaming-playlist'
diff --git a/server/typings/models/video/schedule-video-update.ts b/server/typings/models/video/schedule-video-update.ts
new file mode 100644
index 000000000..ada9af06e
--- /dev/null
+++ b/server/typings/models/video/schedule-video-update.ts
@@ -0,0 +1,9 @@
1import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
2
3export type MScheduleVideoUpdate = Omit<ScheduleVideoUpdateModel, 'Video'>
4
5// ############################################################################
6
7// Format for API or AP object
8
9export type MScheduleVideoUpdateFormattable = Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>
diff --git a/server/typings/models/video/tag.ts b/server/typings/models/video/tag.ts
new file mode 100644
index 000000000..64a68873e
--- /dev/null
+++ b/server/typings/models/video/tag.ts
@@ -0,0 +1,3 @@
1import { TagModel } from '../../../models/video/tag'
2
3export type MTag = Omit<TagModel, 'Videos'>
diff --git a/server/typings/models/video/thumbnail.ts b/server/typings/models/video/thumbnail.ts
new file mode 100644
index 000000000..c03ba55ac
--- /dev/null
+++ b/server/typings/models/video/thumbnail.ts
@@ -0,0 +1,3 @@
1import { ThumbnailModel } from '../../../models/video/thumbnail'
2
3export type MThumbnail = Omit<ThumbnailModel, 'Video' | 'VideoPlaylist'>
diff --git a/server/typings/models/video/video-abuse.ts b/server/typings/models/video/video-abuse.ts
new file mode 100644
index 000000000..e38c3f586
--- /dev/null
+++ b/server/typings/models/video/video-abuse.ts
@@ -0,0 +1,31 @@
1import { VideoAbuseModel } from '../../../models/video/video-abuse'
2import { PickWith } from '../../utils'
3import { MVideo } from './video'
4import { MAccountDefault, MAccountFormattable } from '../account'
5
6type Use<K extends keyof VideoAbuseModel, M> = PickWith<VideoAbuseModel, K, M>
7
8// ############################################################################
9
10export type MVideoAbuse = Omit<VideoAbuseModel, 'Account' | 'Video' | 'toActivityPubObject'>
11
12// ############################################################################
13
14export type MVideoAbuseId = Pick<VideoAbuseModel, 'id'>
15
16export type MVideoAbuseVideo = MVideoAbuse &
17 Pick<VideoAbuseModel, 'toActivityPubObject'> &
18 Use<'Video', MVideo>
19
20export type MVideoAbuseAccountVideo = MVideoAbuse &
21 Pick<VideoAbuseModel, 'toActivityPubObject'> &
22 Use<'Video', MVideo> &
23 Use<'Account', MAccountDefault>
24
25// ############################################################################
26
27// Format for API or AP object
28
29export type MVideoAbuseFormattable = MVideoAbuse &
30 Use<'Account', MAccountFormattable> &
31 Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name'>>
diff --git a/server/typings/models/video/video-blacklist.ts b/server/typings/models/video/video-blacklist.ts
new file mode 100644
index 000000000..1dedfa37f
--- /dev/null
+++ b/server/typings/models/video/video-blacklist.ts
@@ -0,0 +1,24 @@
1import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
2import { PickWith } from '@server/typings/utils'
3import { MVideo, MVideoFormattable } from '@server/typings/models'
4
5type Use<K extends keyof VideoBlacklistModel, M> = PickWith<VideoBlacklistModel, K, M>
6
7// ############################################################################
8
9export type MVideoBlacklist = Omit<VideoBlacklistModel, 'Video'>
10
11export type MVideoBlacklistLight = Pick<MVideoBlacklist, 'id' | 'reason' | 'unfederated'>
12export type MVideoBlacklistUnfederated = Pick<MVideoBlacklist, 'unfederated'>
13
14// ############################################################################
15
16export type MVideoBlacklistVideo = MVideoBlacklist &
17 Use<'Video', MVideo>
18
19// ############################################################################
20
21// Format for API or AP object
22
23export type MVideoBlacklistFormattable = MVideoBlacklist &
24 Use<'Video', MVideoFormattable>
diff --git a/server/typings/models/video/video-caption.ts b/server/typings/models/video/video-caption.ts
new file mode 100644
index 000000000..7cb2a2ad3
--- /dev/null
+++ b/server/typings/models/video/video-caption.ts
@@ -0,0 +1,24 @@
1import { VideoCaptionModel } from '../../../models/video/video-caption'
2import { FunctionProperties, PickWith } from '@server/typings/utils'
3import { MVideo, MVideoUUID } from '@server/typings/models'
4
5type Use<K extends keyof VideoCaptionModel, M> = PickWith<VideoCaptionModel, K, M>
6
7// ############################################################################
8
9export type MVideoCaption = Omit<VideoCaptionModel, 'Video'>
10
11// ############################################################################
12
13export type MVideoCaptionLanguage = Pick<MVideoCaption, 'language'>
14
15export type MVideoCaptionVideo = MVideoCaption &
16 Use<'Video', Pick<MVideo, 'id' | 'remote' | 'uuid'>>
17
18// ############################################################################
19
20// Format for API or AP object
21
22export type MVideoCaptionFormattable = FunctionProperties<MVideoCaption> &
23 Pick<MVideoCaption, 'language'> &
24 Use<'Video', MVideoUUID>
diff --git a/server/typings/models/video/video-change-ownership.ts b/server/typings/models/video/video-change-ownership.ts
new file mode 100644
index 000000000..72634cdb2
--- /dev/null
+++ b/server/typings/models/video/video-change-ownership.ts
@@ -0,0 +1,23 @@
1import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership'
2import { PickWith } from '@server/typings/utils'
3import { MAccountDefault, MAccountFormattable, MVideo, MVideoWithFileThumbnail } from '@server/typings/models'
4
5type Use<K extends keyof VideoChangeOwnershipModel, M> = PickWith<VideoChangeOwnershipModel, K, M>
6
7// ############################################################################
8
9export type MVideoChangeOwnership = Omit<VideoChangeOwnershipModel, 'Initiator' | 'NextOwner' | 'Video'>
10
11export type MVideoChangeOwnershipFull = MVideoChangeOwnership &
12 Use<'Initiator', MAccountDefault> &
13 Use<'NextOwner', MAccountDefault> &
14 Use<'Video', MVideoWithFileThumbnail>
15
16// ############################################################################
17
18// Format for API or AP object
19
20export type MVideoChangeOwnershipFormattable = Pick<MVideoChangeOwnership, 'id' | 'status' | 'createdAt'> &
21 Use<'Initiator', MAccountFormattable> &
22 Use<'NextOwner', MAccountFormattable> &
23 Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'url' | 'name'>>
diff --git a/server/typings/models/video/video-channels.ts b/server/typings/models/video/video-channels.ts
new file mode 100644
index 000000000..292d0ac95
--- /dev/null
+++ b/server/typings/models/video/video-channels.ts
@@ -0,0 +1,126 @@
1import { FunctionProperties, PickWith, PickWithOpt } from '../../utils'
2import { VideoChannelModel } from '../../../models/video/video-channel'
3import {
4 MAccountActor,
5 MAccountAPI,
6 MAccountDefault,
7 MAccountFormattable,
8 MAccountLight,
9 MAccountSummaryBlocks,
10 MAccountSummaryFormattable,
11 MAccountUrl,
12 MAccountUserId,
13 MActor,
14 MActorAccountChannelId,
15 MActorAP,
16 MActorAPI,
17 MActorDefault,
18 MActorDefaultLight,
19 MActorFormattable,
20 MActorLight,
21 MActorSummary,
22 MActorSummaryFormattable, MActorUrl
23} from '../account'
24import { MVideo } from './video'
25
26type Use<K extends keyof VideoChannelModel, M> = PickWith<VideoChannelModel, K, M>
27
28// ############################################################################
29
30export type MChannel = Omit<VideoChannelModel, 'Actor' | 'Account' | 'Videos' | 'VideoPlaylists'>
31
32// ############################################################################
33
34export type MChannelId = Pick<MChannel, 'id'>
35
36// ############################################################################
37
38export type MChannelIdActor = MChannelId &
39 Use<'Actor', MActorAccountChannelId>
40
41export type MChannelUserId = Pick<MChannel, 'accountId'> &
42 Use<'Account', MAccountUserId>
43
44export type MChannelActor = MChannel &
45 Use<'Actor', MActor>
46
47export type MChannelUrl = Use<'Actor', MActorUrl>
48
49// Default scope
50export type MChannelDefault = MChannel &
51 Use<'Actor', MActorDefault>
52
53// ############################################################################
54
55// Not all association attributes
56
57export type MChannelLight = MChannel &
58 Use<'Actor', MActorDefaultLight>
59
60export type MChannelActorLight = MChannel &
61 Use<'Actor', MActorLight>
62
63export type MChannelAccountLight = MChannel &
64 Use<'Actor', MActorDefaultLight> &
65 Use<'Account', MAccountLight>
66
67// ############################################################################
68
69// Account associations
70
71export type MChannelAccountActor = MChannel &
72 Use<'Account', MAccountActor>
73
74export type MChannelAccountDefault = MChannel &
75 Use<'Actor', MActorDefault> &
76 Use<'Account', MAccountDefault>
77
78export type MChannelActorAccountActor = MChannel &
79 Use<'Account', MAccountActor> &
80 Use<'Actor', MActor>
81
82// ############################################################################
83
84// Videos associations
85export type MChannelVideos = MChannel &
86 Use<'Videos', MVideo[]>
87
88export type MChannelActorAccountDefaultVideos = MChannel &
89 Use<'Actor', MActorDefault> &
90 Use<'Account', MAccountDefault> &
91 Use<'Videos', MVideo[]>
92
93// ############################################################################
94
95// For API
96
97export type MChannelSummary = FunctionProperties<MChannel> &
98 Pick<MChannel, 'id' | 'name' | 'description' | 'actorId'> &
99 Use<'Actor', MActorSummary>
100
101export type MChannelSummaryAccount = MChannelSummary &
102 Use<'Account', MAccountSummaryBlocks>
103
104export type MChannelAPI = MChannel &
105 Use<'Actor', MActorAPI> &
106 Use<'Account', MAccountAPI>
107
108// ############################################################################
109
110// Format for API or AP object
111
112export type MChannelSummaryFormattable = FunctionProperties<MChannel> &
113 Pick<MChannel, 'id' | 'name'> &
114 Use<'Actor', MActorSummaryFormattable>
115
116export type MChannelAccountSummaryFormattable = MChannelSummaryFormattable &
117 Use<'Account', MAccountSummaryFormattable>
118
119export type MChannelFormattable = FunctionProperties<MChannel> &
120 Pick<MChannel, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'support'> &
121 Use<'Actor', MActorFormattable> &
122 PickWithOpt<VideoChannelModel, 'Account', MAccountFormattable>
123
124export type MChannelAP = Pick<MChannel, 'name' | 'description' | 'support'> &
125 Use<'Actor', MActorAP> &
126 Use<'Account', MAccountUrl>
diff --git a/server/typings/models/video/video-comment.ts b/server/typings/models/video/video-comment.ts
new file mode 100644
index 000000000..4fd1c29e8
--- /dev/null
+++ b/server/typings/models/video/video-comment.ts
@@ -0,0 +1,57 @@
1import { VideoCommentModel } from '../../../models/video/video-comment'
2import { PickWith, PickWithOpt } from '../../utils'
3import { MAccountDefault, MAccountFormattable, MAccountUrl, MActorUrl } from '../account'
4import { MVideoAccountLight, MVideoFeed, MVideoIdUrl, MVideoUrl } from './video'
5
6type Use<K extends keyof VideoCommentModel, M> = PickWith<VideoCommentModel, K, M>
7
8// ############################################################################
9
10export type MComment = Omit<VideoCommentModel, 'OriginVideoComment' | 'InReplyToVideoComment' | 'Video' | 'Account'>
11export type MCommentTotalReplies = MComment & { totalReplies?: number }
12export type MCommentId = Pick<MComment, 'id'>
13export type MCommentUrl = Pick<MComment, 'url'>
14
15// ############################################################################
16
17export type MCommentOwner = MComment &
18 Use<'Account', MAccountDefault>
19
20export type MCommentVideo = MComment &
21 Use<'Video', MVideoAccountLight>
22
23export type MCommentReply = MComment &
24 Use<'InReplyToVideoComment', MComment>
25
26export type MCommentOwnerVideo = MComment &
27 Use<'Account', MAccountDefault> &
28 Use<'Video', MVideoAccountLight>
29
30export type MCommentOwnerVideoReply = MComment &
31 Use<'Account', MAccountDefault> &
32 Use<'Video', MVideoAccountLight> &
33 Use<'InReplyToVideoComment', MComment>
34
35export type MCommentOwnerReplyVideoLight = MComment &
36 Use<'Account', MAccountDefault> &
37 Use<'InReplyToVideoComment', MComment> &
38 Use<'Video', MVideoIdUrl>
39
40export type MCommentOwnerVideoFeed = MCommentOwner &
41 Use<'Video', MVideoFeed>
42
43// ############################################################################
44
45export type MCommentAPI = MComment & { totalReplies: number }
46
47// ############################################################################
48
49// Format for API or AP object
50
51export type MCommentFormattable = MCommentTotalReplies &
52 Use<'Account', MAccountFormattable>
53
54export type MCommentAP = MComment &
55 Use<'Account', MAccountUrl> &
56 PickWithOpt<VideoCommentModel, 'Video', MVideoUrl> &
57 PickWithOpt<VideoCommentModel, 'InReplyToVideoComment', MCommentUrl>
diff --git a/server/typings/models/video/video-file.ts b/server/typings/models/video/video-file.ts
new file mode 100644
index 000000000..484351a8d
--- /dev/null
+++ b/server/typings/models/video/video-file.ts
@@ -0,0 +1,19 @@
1import { VideoFileModel } from '../../../models/video/video-file'
2import { PickWith, PickWithOpt } from '../../utils'
3import { MVideo, MVideoUUID } from './video'
4import { MVideoRedundancyFileUrl } from './video-redundancy'
5
6type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M>
7
8// ############################################################################
9
10export type MVideoFile = Omit<VideoFileModel, 'Video' | 'RedundancyVideos'>
11
12export type MVideoFileVideo = MVideoFile &
13 Use<'Video', MVideo>
14
15export type MVideoFileVideoUUID = MVideoFile &
16 Use<'Video', MVideoUUID>
17
18export type MVideoFileRedundanciesOpt = MVideoFile &
19 PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]>
diff --git a/server/typings/models/video/video-import.ts b/server/typings/models/video/video-import.ts
new file mode 100644
index 000000000..c6a1c5b66
--- /dev/null
+++ b/server/typings/models/video/video-import.ts
@@ -0,0 +1,31 @@
1import { VideoImportModel } from '@server/models/video/video-import'
2import { PickWith, PickWithOpt } from '@server/typings/utils'
3import { MUser, MVideo, MVideoAccountLight, MVideoFormattable, MVideoTag, MVideoThumbnail, MVideoWithFile } from '@server/typings/models'
4
5type Use<K extends keyof VideoImportModel, M> = PickWith<VideoImportModel, K, M>
6
7// ############################################################################
8
9export type MVideoImport = Omit<VideoImportModel, 'User' | 'Video'>
10
11export type MVideoImportVideo = MVideoImport &
12 Use<'Video', MVideo>
13
14// ############################################################################
15
16type VideoAssociation = MVideoTag & MVideoAccountLight & MVideoThumbnail
17
18export type MVideoImportDefault = MVideoImport &
19 Use<'User', MUser> &
20 Use<'Video', VideoAssociation>
21
22export type MVideoImportDefaultFiles = MVideoImport &
23 Use<'User', MUser> &
24 Use<'Video', VideoAssociation & MVideoWithFile>
25
26// ############################################################################
27
28// Format for API or AP object
29
30export type MVideoImportFormattable = MVideoImport &
31 PickWithOpt<VideoImportModel, 'Video', MVideoFormattable & MVideoTag>
diff --git a/server/typings/models/video/video-playlist-element.ts b/server/typings/models/video/video-playlist-element.ts
new file mode 100644
index 000000000..7b1b993ce
--- /dev/null
+++ b/server/typings/models/video/video-playlist-element.ts
@@ -0,0 +1,34 @@
1import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element'
2import { PickWith } from '@server/typings/utils'
3import { MVideoFormattable, MVideoPlaylistPrivacy, MVideoThumbnail, MVideoUrl } from '@server/typings/models'
4
5type Use<K extends keyof VideoPlaylistElementModel, M> = PickWith<VideoPlaylistElementModel, K, M>
6
7// ############################################################################
8
9export type MVideoPlaylistElement = Omit<VideoPlaylistElementModel, 'VideoPlaylist' | 'Video'>
10
11// ############################################################################
12
13export type MVideoPlaylistElementId = Pick<MVideoPlaylistElement, 'id'>
14
15export type MVideoPlaylistElementLight = Pick<MVideoPlaylistElement, 'id' | 'videoId' | 'startTimestamp' | 'stopTimestamp'>
16
17// ############################################################################
18
19export type MVideoPlaylistVideoThumbnail = MVideoPlaylistElement &
20 Use<'Video', MVideoThumbnail>
21
22export type MVideoPlaylistElementVideoUrlPlaylistPrivacy = MVideoPlaylistElement &
23 Use<'Video', MVideoUrl> &
24 Use<'VideoPlaylist', MVideoPlaylistPrivacy>
25
26// ############################################################################
27
28// Format for API or AP object
29
30export type MVideoPlaylistElementFormattable = MVideoPlaylistElement &
31 Use<'Video', MVideoFormattable>
32
33export type MVideoPlaylistElementAP = MVideoPlaylistElement &
34 Use<'Video', MVideoUrl>
diff --git a/server/typings/models/video/video-playlist.ts b/server/typings/models/video/video-playlist.ts
new file mode 100644
index 000000000..a40c7aca9
--- /dev/null
+++ b/server/typings/models/video/video-playlist.ts
@@ -0,0 +1,92 @@
1import { VideoPlaylistModel } from '../../../models/video/video-playlist'
2import { PickWith } from '../../utils'
3import { MAccount, MAccountDefault, MAccountSummary, MAccountSummaryFormattable } from '../account'
4import { MThumbnail } from './thumbnail'
5import { MChannelDefault, MChannelSummary, MChannelSummaryFormattable, MChannelUrl } from './video-channels'
6import { MVideoPlaylistElementLight } from '@server/typings/models/video/video-playlist-element'
7
8type Use<K extends keyof VideoPlaylistModel, M> = PickWith<VideoPlaylistModel, K, M>
9
10// ############################################################################
11
12export type MVideoPlaylist = Omit<VideoPlaylistModel, 'OwnerAccount' | 'VideoChannel' | 'VideoPlaylistElements' | 'Thumbnail'>
13
14// ############################################################################
15
16export type MVideoPlaylistId = Pick<MVideoPlaylist, 'id'>
17export type MVideoPlaylistPrivacy = Pick<MVideoPlaylist, 'privacy'>
18export type MVideoPlaylistUUID = Pick<MVideoPlaylist, 'uuid'>
19export type MVideoPlaylistVideosLength = MVideoPlaylist & { videosLength?: number }
20
21// ############################################################################
22
23// With elements
24
25export type MVideoPlaylistWithElements = MVideoPlaylist &
26 Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]>
27
28export type MVideoPlaylistIdWithElements = MVideoPlaylistId &
29 Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]>
30
31// ############################################################################
32
33// With account
34
35export type MVideoPlaylistOwner = MVideoPlaylist &
36 Use<'OwnerAccount', MAccount>
37
38export type MVideoPlaylistOwnerDefault = MVideoPlaylist &
39 Use<'OwnerAccount', MAccountDefault>
40
41// ############################################################################
42
43// With thumbnail
44
45export type MVideoPlaylistThumbnail = MVideoPlaylist &
46 Use<'Thumbnail', MThumbnail>
47
48export type MVideoPlaylistAccountThumbnail = MVideoPlaylist &
49 Use<'OwnerAccount', MAccountDefault> &
50 Use<'Thumbnail', MThumbnail>
51
52// ############################################################################
53
54// With channel
55
56export type MVideoPlaylistAccountChannelDefault = MVideoPlaylist &
57 Use<'OwnerAccount', MAccountDefault> &
58 Use<'VideoChannel', MChannelDefault>
59
60// ############################################################################
61
62// With all associations
63
64export type MVideoPlaylistFull = MVideoPlaylist &
65 Use<'OwnerAccount', MAccountDefault> &
66 Use<'VideoChannel', MChannelDefault> &
67 Use<'Thumbnail', MThumbnail>
68
69// ############################################################################
70
71// For API
72
73export type MVideoPlaylistAccountChannelSummary = MVideoPlaylist &
74 Use<'OwnerAccount', MAccountSummary> &
75 Use<'VideoChannel', MChannelSummary>
76
77export type MVideoPlaylistFullSummary = MVideoPlaylist &
78 Use<'Thumbnail', MThumbnail> &
79 Use<'OwnerAccount', MAccountSummary> &
80 Use<'VideoChannel', MChannelSummary>
81
82// ############################################################################
83
84// Format for API or AP object
85
86export type MVideoPlaylistFormattable = MVideoPlaylistVideosLength &
87 Use<'OwnerAccount', MAccountSummaryFormattable> &
88 Use<'VideoChannel', MChannelSummaryFormattable>
89
90export type MVideoPlaylistAP = MVideoPlaylist &
91 Use<'Thumbnail', MThumbnail> &
92 Use<'VideoChannel', MChannelUrl>
diff --git a/server/typings/models/video/video-rate.ts b/server/typings/models/video/video-rate.ts
new file mode 100644
index 000000000..2ff8a625b
--- /dev/null
+++ b/server/typings/models/video/video-rate.ts
@@ -0,0 +1,23 @@
1import { AccountVideoRateModel } from '@server/models/account/account-video-rate'
2import { PickWith } from '@server/typings/utils'
3import { MAccountAudience, MAccountUrl, MVideo, MVideoFormattable } from '..'
4
5type Use<K extends keyof AccountVideoRateModel, M> = PickWith<AccountVideoRateModel, K, M>
6
7// ############################################################################
8
9export type MAccountVideoRate = Omit<AccountVideoRateModel, 'Video' | 'Account'>
10
11export type MAccountVideoRateAccountUrl = MAccountVideoRate &
12 Use<'Account', MAccountUrl>
13
14export type MAccountVideoRateAccountVideo = MAccountVideoRate &
15 Use<'Account', MAccountAudience> &
16 Use<'Video', MVideo>
17
18// ############################################################################
19
20// Format for API or AP object
21
22export type MAccountVideoRateFormattable = Pick<MAccountVideoRate, 'type'> &
23 Use<'Video', MVideoFormattable>
diff --git a/server/typings/models/video/video-redundancy.ts b/server/typings/models/video/video-redundancy.ts
new file mode 100644
index 000000000..f3846afd7
--- /dev/null
+++ b/server/typings/models/video/video-redundancy.ts
@@ -0,0 +1,38 @@
1import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
2import { PickWith, PickWithOpt } from '@server/typings/utils'
3import { MStreamingPlaylistVideo, MVideoFile, MVideoFileVideo, MVideoUrl } from '@server/typings/models'
4import { VideoStreamingPlaylist } from '../../../../shared/models/videos/video-streaming-playlist.model'
5import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
6import { VideoFile } from '../../../../shared/models/videos'
7import { VideoFileModel } from '@server/models/video/video-file'
8
9type Use<K extends keyof VideoRedundancyModel, M> = PickWith<VideoRedundancyModel, K, M>
10
11// ############################################################################
12
13export type MVideoRedundancy = Omit<VideoRedundancyModel, 'VideoFile' | 'VideoStreamingPlaylist' | 'Actor'>
14
15export type MVideoRedundancyFileUrl = Pick<MVideoRedundancy, 'fileUrl'>
16
17// ############################################################################
18
19export type MVideoRedundancyFile = MVideoRedundancy &
20 Use<'VideoFile', MVideoFile>
21
22export type MVideoRedundancyFileVideo = MVideoRedundancy &
23 Use<'VideoFile', MVideoFileVideo>
24
25export type MVideoRedundancyStreamingPlaylistVideo = MVideoRedundancy &
26 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo>
27
28export type MVideoRedundancyVideo = MVideoRedundancy &
29 Use<'VideoFile', MVideoFileVideo> &
30 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo>
31
32// ############################################################################
33
34// Format for API or AP object
35
36export type MVideoRedundancyAP = MVideoRedundancy &
37 PickWithOpt<VideoRedundancyModel, 'VideoFile', MVideoFile & PickWith<VideoFileModel, 'Video', MVideoUrl>> &
38 PickWithOpt<VideoRedundancyModel, 'VideoStreamingPlaylist', PickWith<VideoStreamingPlaylistModel, 'Video', MVideoUrl>>
diff --git a/server/typings/models/video/video-share.ts b/server/typings/models/video/video-share.ts
new file mode 100644
index 000000000..a7a90beeb
--- /dev/null
+++ b/server/typings/models/video/video-share.ts
@@ -0,0 +1,17 @@
1import { VideoShareModel } from '../../../models/video/video-share'
2import { PickWith } from '../../utils'
3import { MActorDefault } from '../account'
4import { MVideo } from './video'
5
6type Use<K extends keyof VideoShareModel, M> = PickWith<VideoShareModel, K, M>
7
8// ############################################################################
9
10export type MVideoShare = Omit<VideoShareModel, 'Actor' | 'Video'>
11
12export type MVideoShareActor = MVideoShare &
13 Use<'Actor', MActorDefault>
14
15export type MVideoShareFull = MVideoShare &
16 Use<'Actor', MActorDefault> &
17 Use<'Video', MVideo>
diff --git a/server/typings/models/video/video-streaming-playlist.ts b/server/typings/models/video/video-streaming-playlist.ts
new file mode 100644
index 000000000..79696bcff
--- /dev/null
+++ b/server/typings/models/video/video-streaming-playlist.ts
@@ -0,0 +1,19 @@
1import { VideoStreamingPlaylistModel } from '../../../models/video/video-streaming-playlist'
2import { PickWith, PickWithOpt } from '../../utils'
3import { MVideoRedundancyFileUrl } from './video-redundancy'
4import { MVideo, MVideoUrl } from '@server/typings/models'
5
6type Use<K extends keyof VideoStreamingPlaylistModel, M> = PickWith<VideoStreamingPlaylistModel, K, M>
7
8// ############################################################################
9
10export type MStreamingPlaylist = Omit<VideoStreamingPlaylistModel, 'Video' | 'RedundancyVideos'>
11
12export type MStreamingPlaylistVideo = MStreamingPlaylist &
13 Use<'Video', MVideo>
14
15export type MStreamingPlaylistRedundancies = MStreamingPlaylist &
16 Use<'RedundancyVideos', MVideoRedundancyFileUrl[]>
17
18export type MStreamingPlaylistRedundanciesOpt = MStreamingPlaylist &
19 PickWithOpt<VideoStreamingPlaylistModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]>
diff --git a/server/typings/models/video/video.ts b/server/typings/models/video/video.ts
new file mode 100644
index 000000000..9a53bd337
--- /dev/null
+++ b/server/typings/models/video/video.ts
@@ -0,0 +1,173 @@
1import { VideoModel } from '../../../models/video/video'
2import { PickWith, PickWithOpt } from '../../utils'
3import {
4 MChannelAccountDefault,
5 MChannelAccountLight,
6 MChannelAccountSummaryFormattable,
7 MChannelActor,
8 MChannelFormattable,
9 MChannelUserId
10} from './video-channels'
11import { MTag } from './tag'
12import { MVideoCaptionLanguage } from './video-caption'
13import { MStreamingPlaylist, MStreamingPlaylistRedundancies, MStreamingPlaylistRedundanciesOpt } from './video-streaming-playlist'
14import { MVideoFile, MVideoFileRedundanciesOpt } from './video-file'
15import { MThumbnail } from './thumbnail'
16import { MVideoBlacklist, MVideoBlacklistLight, MVideoBlacklistUnfederated } from './video-blacklist'
17import { MScheduleVideoUpdate } from './schedule-video-update'
18import { MUserVideoHistoryTime } from '../user/user-video-history'
19
20type Use<K extends keyof VideoModel, M> = PickWith<VideoModel, K, M>
21
22// ############################################################################
23
24export type MVideo = Omit<VideoModel, 'VideoChannel' | 'Tags' | 'Thumbnails' | 'VideoPlaylistElements' | 'VideoAbuses' |
25 'VideoFiles' | 'VideoStreamingPlaylists' | 'VideoShares' | 'AccountVideoRates' | 'VideoComments' | 'VideoViews' | 'UserVideoHistories' |
26 'ScheduleVideoUpdate' | 'VideoBlacklist' | 'VideoImport' | 'VideoCaptions'>
27
28// ############################################################################
29
30export type MVideoId = Pick<MVideo, 'id'>
31export type MVideoUrl = Pick<MVideo, 'url'>
32export type MVideoUUID = Pick<MVideo, 'uuid'>
33
34export type MVideoIdUrl = MVideoId & MVideoUrl
35export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'>
36
37// ############################################################################
38
39// Video raw associations: schedules, video files, tags, thumbnails, captions, streaming playlists
40
41// "With" to not confuse with the VideoFile model
42export type MVideoWithFile = MVideo &
43 Use<'VideoFiles', MVideoFile[]>
44
45export type MVideoThumbnail = MVideo &
46 Use<'Thumbnails', MThumbnail[]>
47
48export type MVideoIdThumbnail = MVideoId &
49 Use<'Thumbnails', MThumbnail[]>
50
51export type MVideoWithFileThumbnail = MVideo &
52 Use<'VideoFiles', MVideoFile[]> &
53 Use<'Thumbnails', MThumbnail[]>
54
55export type MVideoThumbnailBlacklist = MVideo &
56 Use<'Thumbnails', MThumbnail[]> &
57 Use<'VideoBlacklist', MVideoBlacklistLight>
58
59export type MVideoTag = MVideo &
60 Use<'Tags', MTag[]>
61
62export type MVideoWithSchedule = MVideo &
63 PickWithOpt<VideoModel, 'ScheduleVideoUpdate', MScheduleVideoUpdate>
64
65export type MVideoWithCaptions = MVideo &
66 Use<'VideoCaptions', MVideoCaptionLanguage[]>
67
68export type MVideoWithStreamingPlaylist = MVideo &
69 Use<'VideoStreamingPlaylists', MStreamingPlaylist[]>
70
71// ############################################################################
72
73// Associations with not all their attributes
74
75export type MVideoUserHistory = MVideo &
76 Use<'UserVideoHistories', MUserVideoHistoryTime[]>
77
78export type MVideoWithBlacklistLight = MVideo &
79 Use<'VideoBlacklist', MVideoBlacklistLight>
80
81export type MVideoAccountLight = MVideo &
82 Use<'VideoChannel', MChannelAccountLight>
83
84export type MVideoWithRights = MVideo &
85 Use<'VideoBlacklist', MVideoBlacklistLight> &
86 Use<'Thumbnails', MThumbnail[]> &
87 Use<'VideoChannel', MChannelUserId>
88
89// ############################################################################
90
91// All files with some additional associations
92
93export type MVideoWithAllFiles = MVideo &
94 Use<'VideoFiles', MVideoFile[]> &
95 Use<'Thumbnails', MThumbnail[]> &
96 Use<'VideoStreamingPlaylists', MStreamingPlaylist[]>
97
98export type MVideoAccountLightBlacklistAllFiles = MVideo &
99 Use<'VideoFiles', MVideoFile[]> &
100 Use<'Thumbnails', MThumbnail[]> &
101 Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> &
102 Use<'VideoChannel', MChannelAccountLight> &
103 Use<'VideoBlacklist', MVideoBlacklistLight>
104
105// ############################################################################
106
107// With account
108
109export type MVideoAccountDefault = MVideo &
110 Use<'VideoChannel', MChannelAccountDefault>
111
112export type MVideoThumbnailAccountDefault = MVideo &
113 Use<'Thumbnails', MThumbnail[]> &
114 Use<'VideoChannel', MChannelAccountDefault>
115
116export type MVideoWithChannelActor = MVideo &
117 Use<'VideoChannel', MChannelActor>
118
119export type MVideoFullLight = MVideo &
120 Use<'Thumbnails', MThumbnail[]> &
121 Use<'VideoBlacklist', MVideoBlacklistLight> &
122 Use<'Tags', MTag[]> &
123 Use<'VideoChannel', MChannelAccountLight> &
124 Use<'UserVideoHistories', MUserVideoHistoryTime[]> &
125 Use<'VideoFiles', MVideoFile[]> &
126 Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> &
127 Use<'VideoStreamingPlaylists', MStreamingPlaylist[]>
128
129// ############################################################################
130
131// API
132
133export type MVideoAP = MVideo &
134 Use<'Tags', MTag[]> &
135 Use<'VideoChannel', MChannelAccountLight> &
136 Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> &
137 Use<'VideoCaptions', MVideoCaptionLanguage[]> &
138 Use<'VideoBlacklist', MVideoBlacklistUnfederated> &
139 Use<'VideoFiles', MVideoFileRedundanciesOpt[]>
140
141export type MVideoAPWithoutCaption = Omit<MVideoAP, 'VideoCaptions'>
142
143export type MVideoDetails = MVideo &
144 Use<'VideoBlacklist', MVideoBlacklistLight> &
145 Use<'Tags', MTag[]> &
146 Use<'VideoChannel', MChannelAccountLight> &
147 Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> &
148 Use<'Thumbnails', MThumbnail[]> &
149 Use<'UserVideoHistories', MUserVideoHistoryTime[]> &
150 Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundancies[]> &
151 Use<'VideoFiles', MVideoFileRedundanciesOpt[]>
152
153export type MVideoForUser = MVideo &
154 Use<'VideoChannel', MChannelAccountDefault> &
155 Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> &
156 Use<'VideoBlacklist', MVideoBlacklistLight> &
157 Use<'Thumbnails', MThumbnail[]>
158
159// ############################################################################
160
161// Format for API or AP object
162
163export type MVideoFormattable = MVideo &
164 PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> &
165 Use<'VideoChannel', MChannelAccountSummaryFormattable> &
166 PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> &
167 PickWithOpt<VideoModel, 'VideoBlacklist', Pick<MVideoBlacklist, 'reason'>>
168
169export type MVideoFormattableDetails = MVideoFormattable &
170 Use<'VideoChannel', MChannelFormattable> &
171 Use<'Tags', MTag[]> &
172 Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesOpt[]> &
173 Use<'VideoFiles', MVideoFileRedundanciesOpt[]>
diff --git a/server/typings/utils.ts b/server/typings/utils.ts
index a86b05be2..4b5cf4d7e 100644
--- a/server/typings/utils.ts
+++ b/server/typings/utils.ts
@@ -1,3 +1,13 @@
1export type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] 1export type FunctionPropertyNames<T> = {
2 [K in keyof T]: T[K] extends Function ? K : never
3}[keyof T]
2 4
3export type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>> 5export type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>
6
7export type PickWith<T, KT extends keyof T, V> = {
8 [P in KT]: T[P] extends V ? V : never
9}
10
11export type PickWithOpt<T, KT extends keyof T, V> = {
12 [P in KT]?: T[P] extends V ? V : never
13}
diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts
index 95801190d..492b672c7 100644
--- a/shared/models/activitypub/activity.ts
+++ b/shared/models/activitypub/activity.ts
@@ -91,5 +91,5 @@ export interface ActivityDislike extends BaseActivity {
91export interface ActivityFlag extends BaseActivity { 91export interface ActivityFlag extends BaseActivity {
92 type: 'Flag', 92 type: 'Flag',
93 content: string, 93 content: string,
94 object: APObject 94 object: APObject | APObject[]
95} 95}
diff --git a/shared/models/activitypub/objects/video-abuse-object.ts b/shared/models/activitypub/objects/video-abuse-object.ts
index 40e7abd57..5f1264a76 100644
--- a/shared/models/activitypub/objects/video-abuse-object.ts
+++ b/shared/models/activitypub/objects/video-abuse-object.ts
@@ -1,5 +1,5 @@
1export interface VideoAbuseObject { 1export interface VideoAbuseObject {
2 type: 'Flag', 2 type: 'Flag',
3 content: string 3 content: string
4 object: string 4 object: string | string[]
5} 5}
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml
index 24ffdbc15..69717525d 100644
--- a/support/doc/api/openapi.yaml
+++ b/support/doc/api/openapi.yaml
@@ -130,9 +130,6 @@ paths:
130 summary: Get the account by name 130 summary: Get the account by name
131 parameters: 131 parameters:
132 - $ref: '#/components/parameters/name' 132 - $ref: '#/components/parameters/name'
133 - $ref: '#/components/parameters/start'
134 - $ref: '#/components/parameters/count'
135 - $ref: '#/components/parameters/sort'
136 responses: 133 responses:
137 '200': 134 '200':
138 description: successful operation 135 description: successful operation
@@ -204,6 +201,10 @@ paths:
204 tags: 201 tags:
205 - Accounts 202 - Accounts
206 summary: Get all accounts 203 summary: Get all accounts
204 parameters:
205 - $ref: '#/components/parameters/start'
206 - $ref: '#/components/parameters/count'
207 - $ref: '#/components/parameters/sort'
207 responses: 208 responses:
208 '200': 209 '200':
209 description: successful operation 210 description: successful operation
@@ -233,6 +234,10 @@ paths:
233 responses: 234 responses:
234 '200': 235 '200':
235 description: successful operation 236 description: successful operation
237 content:
238 application/json:
239 schema:
240 $ref: '#/components/schemas/ServerConfigAbout'
236 /config/custom: 241 /config/custom:
237 get: 242 get:
238 summary: Get the runtime configuration of the server 243 summary: Get the runtime configuration of the server
@@ -244,6 +249,10 @@ paths:
244 responses: 249 responses:
245 '200': 250 '200':
246 description: successful operation 251 description: successful operation
252 content:
253 application/json:
254 schema:
255 $ref: '#/components/schemas/ServerConfigCustom'
247 put: 256 put:
248 summary: Set the runtime configuration of the server 257 summary: Set the runtime configuration of the server
249 tags: 258 tags:
@@ -726,8 +735,7 @@ paths:
726 type: string 735 type: string
727 format: binary 736 format: binary
728 encoding: 737 encoding:
729 profileImage: 738 avatarfile:
730 # only accept png/jpeg
731 contentType: image/png, image/jpeg 739 contentType: image/png, image/jpeg
732 /videos: 740 /videos:
733 get: 741 get:
@@ -829,9 +837,11 @@ paths:
829 thumbnailfile: 837 thumbnailfile:
830 description: Video thumbnail file 838 description: Video thumbnail file
831 type: string 839 type: string
840 format: binary
832 previewfile: 841 previewfile:
833 description: Video preview file 842 description: Video preview file
834 type: string 843 type: string
844 format: binary
835 category: 845 category:
836 description: Video category 846 description: Video category
837 type: string 847 type: string
@@ -874,6 +884,11 @@ paths:
874 format: date-time 884 format: date-time
875 scheduleUpdate: 885 scheduleUpdate:
876 $ref: '#/components/schemas/VideoScheduledUpdate' 886 $ref: '#/components/schemas/VideoScheduledUpdate'
887 encoding:
888 thumbnailfile:
889 contentType: image/jpeg
890 previewfile:
891 contentType: image/jpeg
877 get: 892 get:
878 summary: Get a video by its id 893 summary: Get a video by its id
879 tags: 894 tags:
@@ -1029,9 +1044,11 @@ paths:
1029 thumbnailfile: 1044 thumbnailfile:
1030 description: Video thumbnail file 1045 description: Video thumbnail file
1031 type: string 1046 type: string
1047 format: binary
1032 previewfile: 1048 previewfile:
1033 description: Video preview file 1049 description: Video preview file
1034 type: string 1050 type: string
1051 format: binary
1035 privacy: 1052 privacy:
1036 $ref: '#/components/schemas/VideoPrivacySet' 1053 $ref: '#/components/schemas/VideoPrivacySet'
1037 category: 1054 category:
@@ -1080,6 +1097,13 @@ paths:
1080 - videofile 1097 - videofile
1081 - channelId 1098 - channelId
1082 - name 1099 - name
1100 encoding:
1101 videofile:
1102 contentType: video/mp4, video/webm, video/ogg, video/avi, video/quicktime, video/x-msvideo, video/x-flv, video/x-matroska, application/octet-stream
1103 thumbnailfile:
1104 contentType: image/jpeg
1105 previewfile:
1106 contentType: image/jpeg
1083 x-code-samples: 1107 x-code-samples:
1084 - lang: Shell 1108 - lang: Shell
1085 source: | 1109 source: |
@@ -1142,9 +1166,11 @@ paths:
1142 thumbnailfile: 1166 thumbnailfile:
1143 description: Video thumbnail file 1167 description: Video thumbnail file
1144 type: string 1168 type: string
1169 format: binary
1145 previewfile: 1170 previewfile:
1146 description: Video preview file 1171 description: Video preview file
1147 type: string 1172 type: string
1173 format: binary
1148 privacy: 1174 privacy:
1149 $ref: '#/components/schemas/VideoPrivacySet' 1175 $ref: '#/components/schemas/VideoPrivacySet'
1150 category: 1176 category:
@@ -1188,6 +1214,13 @@ paths:
1188 required: 1214 required:
1189 - channelId 1215 - channelId
1190 - name 1216 - name
1217 encoding:
1218 torrentfile:
1219 contentType: application/x-bittorrent
1220 thumbnailfile:
1221 contentType: image/jpeg
1222 previewfile:
1223 contentType: image/jpeg
1191 /videos/abuse: 1224 /videos/abuse:
1192 get: 1225 get:
1193 summary: Get list of reported video abuses 1226 summary: Get list of reported video abuses
@@ -1308,6 +1341,9 @@ paths:
1308 description: The file to upload. 1341 description: The file to upload.
1309 type: string 1342 type: string
1310 format: binary 1343 format: binary
1344 encoding:
1345 captionfile:
1346 contentType: text/vtt, application/x-subrip
1311 responses: 1347 responses:
1312 '204': 1348 '204':
1313 $ref: '#/paths/~1users~1me/put/responses/204' 1349 $ref: '#/paths/~1users~1me/put/responses/204'
@@ -1952,7 +1988,7 @@ components:
1952 description: 'Video file size in bytes' 1988 description: 'Video file size in bytes'
1953 torrentUrl: 1989 torrentUrl:
1954 type: string 1990 type: string
1955 torrentDownaloadUrl: 1991 torrentDownloadUrl:
1956 type: string 1992 type: string
1957 fileUrl: 1993 fileUrl:
1958 type: string 1994 type: string
@@ -2227,8 +2263,6 @@ components:
2227 properties: 2263 properties:
2228 id: 2264 id:
2229 type: number 2265 type: number
2230 uuid:
2231 type: string
2232 url: 2266 url:
2233 type: string 2267 type: string
2234 name: 2268 name:
@@ -2249,8 +2283,12 @@ components:
2249 allOf: 2283 allOf:
2250 - $ref: '#/components/schemas/Actor' 2284 - $ref: '#/components/schemas/Actor'
2251 - properties: 2285 - properties:
2286 userId:
2287 type: string
2252 displayName: 2288 displayName:
2253 type: string 2289 type: string
2290 description:
2291 type: string
2254 User: 2292 User:
2255 properties: 2293 properties:
2256 id: 2294 id:
@@ -2294,18 +2332,102 @@ components:
2294 type: number 2332 type: number
2295 ServerConfig: 2333 ServerConfig:
2296 properties: 2334 properties:
2335 instance:
2336 type: object
2337 properties:
2338 name:
2339 type: string
2340 shortDescription:
2341 type: string
2342 defaultClientRoute:
2343 type: string
2344 isNSFW:
2345 type: boolean
2346 defaultNSFWPolicy:
2347 type: string
2348 customizations:
2349 type: object
2350 properties:
2351 javascript:
2352 type: string
2353 css:
2354 type: string
2355 plugin:
2356 type: object
2357 properties:
2358 registered:
2359 type: array
2360 items:
2361 type: string
2362 theme:
2363 type: object
2364 properties:
2365 registered:
2366 type: array
2367 items:
2368 type: string
2369 email:
2370 type: object
2371 properties:
2372 enabled:
2373 type: boolean
2374 contactForm:
2375 type: object
2376 properties:
2377 enabled:
2378 type: boolean
2379 serverVersion:
2380 type: string
2381 serverCommit:
2382 type: string
2297 signup: 2383 signup:
2298 type: object 2384 type: object
2299 properties: 2385 properties:
2300 allowed: 2386 allowed:
2301 type: boolean 2387 type: boolean
2388 allowedForCurrentIP:
2389 type: boolean
2390 requiresEmailVerification:
2391 type: boolean
2302 transcoding: 2392 transcoding:
2303 type: object 2393 type: object
2304 properties: 2394 properties:
2395 hls:
2396 type: object
2397 properties:
2398 enabled:
2399 type: boolean
2305 enabledResolutions: 2400 enabledResolutions:
2306 type: array 2401 type: array
2307 items: 2402 items:
2308 type: number 2403 type: number
2404 import:
2405 type: object
2406 properties:
2407 videos:
2408 type: object
2409 properties:
2410 http:
2411 type: object
2412 properties:
2413 enabled:
2414 type: boolean
2415 torrent:
2416 type: object
2417 properties:
2418 enabled:
2419 type: boolean
2420 autoBlacklist:
2421 type: object
2422 properties:
2423 videos:
2424 type: object
2425 properties:
2426 ofUsers:
2427 type: object
2428 properties:
2429 enabled:
2430 type: boolean
2309 avatar: 2431 avatar:
2310 type: object 2432 type: object
2311 properties: 2433 properties:
@@ -2324,6 +2446,18 @@ components:
2324 video: 2446 video:
2325 type: object 2447 type: object
2326 properties: 2448 properties:
2449 image:
2450 type: object
2451 properties:
2452 extensions:
2453 type: array
2454 items:
2455 type: string
2456 size:
2457 type: object
2458 properties:
2459 max:
2460 type: number
2327 file: 2461 file:
2328 type: object 2462 type: object
2329 properties: 2463 properties:
@@ -2331,6 +2465,202 @@ components:
2331 type: array 2465 type: array
2332 items: 2466 items:
2333 type: string 2467 type: string
2468 videoCaption:
2469 type: object
2470 properties:
2471 file:
2472 type: object
2473 properties:
2474 size:
2475 type: object
2476 properties:
2477 max:
2478 type: number
2479 extensions:
2480 type: array
2481 items:
2482 type: string
2483 user:
2484 type: object
2485 properties:
2486 videoQuota:
2487 type: number
2488 videoQuotaDaily:
2489 type: number
2490 trending:
2491 type: object
2492 properties:
2493 videos:
2494 type: object
2495 properties:
2496 intervalDays:
2497 type: number
2498 tracker:
2499 ype: object
2500 properties:
2501 enabled:
2502 type: boolean
2503 ServerConfigAbout:
2504 properties:
2505 instance:
2506 type: object
2507 properties:
2508 name:
2509 type: string
2510 shortDescription:
2511 type: string
2512 description:
2513 type: string
2514 terms:
2515 type: string
2516 ServerConfigCustom:
2517 properties:
2518 instance:
2519 type: object
2520 properties:
2521 name:
2522 type: string
2523 shortDescription:
2524 type: string
2525 description:
2526 type: string
2527 terms:
2528 type: string
2529 defaultClientRoute:
2530 type: string
2531 isNSFW:
2532 type: boolean
2533 defaultNSFWPolicy:
2534 type: string
2535 customizations:
2536 type: object
2537 properties:
2538 javascript:
2539 type: string
2540 css:
2541 type: string
2542 theme:
2543 type: object
2544 properties:
2545 default:
2546 type: string
2547 services:
2548 type: object
2549 properties:
2550 twitter:
2551 type: object
2552 properties:
2553 username:
2554 type: string
2555 whitelisted:
2556 type: boolean
2557 cache:
2558 type: object
2559 properties:
2560 previews:
2561 type: object
2562 properties:
2563 size:
2564 type: number
2565 captions:
2566 type: object
2567 properties:
2568 size:
2569 type: number
2570 signup:
2571 type: object
2572 properties:
2573 enabled:
2574 type: boolean
2575 limit:
2576 type: number
2577 requiresEmailVerification:
2578 type: boolean
2579 admin:
2580 type: object
2581 properties:
2582 email:
2583 type: string
2584 contactForm:
2585 type: object
2586 properties:
2587 enabled:
2588 type: boolean
2589 user:
2590 type: object
2591 properties:
2592 videoQuota:
2593 type: number
2594 videoQuotaDaily:
2595 type: number
2596 transcoding:
2597 type: object
2598 properties:
2599 enabled:
2600 type: boolean
2601 allowAdditionalExtensions:
2602 type: boolean
2603 allowAudioFiles:
2604 type: boolean
2605 threads:
2606 type: number
2607 resolutions:
2608 type: object
2609 properties:
2610 240p:
2611 type: boolean
2612 360p:
2613 type: boolean
2614 480p:
2615 type: boolean
2616 720p:
2617 type: boolean
2618 1080p:
2619 type: boolean
2620 2160p:
2621 type: boolean
2622 hls:
2623 type: object
2624 properties:
2625 enabled:
2626 type: boolean
2627 import:
2628 type: object
2629 properties:
2630 videos:
2631 type: object
2632 properties:
2633 http:
2634 type: object
2635 properties:
2636 enabled:
2637 type: boolean
2638 torrent:
2639 type: object
2640 properties:
2641 enabled:
2642 type: boolean
2643 autoBlacklist:
2644 type: object
2645 properties:
2646 videos:
2647 type: object
2648 properties:
2649 ofUsers:
2650 type: object
2651 properties:
2652 enabled:
2653 type: boolean
2654 followers:
2655 type: object
2656 properties:
2657 instance:
2658 type: object
2659 properties:
2660 enabled:
2661 type: boolean
2662 manualApproval:
2663 type: boolean
2334 Follow: 2664 Follow:
2335 properties: 2665 properties:
2336 id: 2666 id:
diff --git a/support/doc/tools.md b/support/doc/tools.md
index cf427ec84..dd2a03db7 100644
--- a/support/doc/tools.md
+++ b/support/doc/tools.md
@@ -11,6 +11,7 @@
11 - [peertube-import-videos.js](#peertube-import-videosjs) 11 - [peertube-import-videos.js](#peertube-import-videosjs)
12 - [peertube-upload.js](#peertube-uploadjs) 12 - [peertube-upload.js](#peertube-uploadjs)
13 - [peertube-watch.js](#peertube-watchjs) 13 - [peertube-watch.js](#peertube-watchjs)
14 - [peertube-plugins.js](#peertube-pluginsjs)
14- [Server tools](#server-tools) 15- [Server tools](#server-tools)
15 - [parse-log](#parse-log) 16 - [parse-log](#parse-log)
16 - [create-transcoding-job.js](#create-transcoding-jobjs) 17 - [create-transcoding-job.js](#create-transcoding-jobjs)
@@ -19,6 +20,7 @@
19 - [optimize-old-videos.js](#optimize-old-videosjs) 20 - [optimize-old-videos.js](#optimize-old-videosjs)
20 - [update-host.js](#update-hostjs) 21 - [update-host.js](#update-hostjs)
21 - [reset-password.js](#reset-passwordjs) 22 - [reset-password.js](#reset-passwordjs)
23 - [plugin install/uninstall](#plugin-installuninstall)
22 - [REPL (Read Eval Print Loop)](#repl-read-eval-print-loop) 24 - [REPL (Read Eval Print Loop)](#repl-read-eval-print-loop)
23 - [.help](#help) 25 - [.help](#help)
24 - [Lodash example](#lodash-example) 26 - [Lodash example](#lodash-example)
@@ -182,6 +184,22 @@ It provides support for different players:
182- chromecast 184- chromecast
183 185
184 186
187#### peertube-plugins.js
188
189Install/update/uninstall or list local or NPM PeerTube plugins:
190
191```
192$ cd ${CLONE}
193$ node dist/server/tools/peertube-plugins.js --help
194$ node dist/server/tools/peertube-plugins.js list --help
195$ node dist/server/tools/peertube-plugins.js install --help
196$ node dist/server/tools/peertube-plugins.js update --help
197$ node dist/server/tools/peertube-plugins.js uninstall --help
198
199$ node dist/server/tools/peertube-plugins.js install --path /my/plugin/path
200$ node dist/server/tools/peertube-plugins.js install --npm-name peertube-theme-example
201```
202
185## Server tools 203## Server tools
186 204
187These scripts should be run on the server, in `peertube-latest` directory. 205These scripts should be run on the server, in `peertube-latest` directory.
@@ -262,22 +280,22 @@ $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production
262The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running. 280The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running.
263If PeerTube is running, you need to restart it for the changes to take effect (whereas with `peertube plugins` CLI, plugins/themes are dynamically loaded on the server). 281If PeerTube is running, you need to restart it for the changes to take effect (whereas with `peertube plugins` CLI, plugins/themes are dynamically loaded on the server).
264 282
265To install a plugin or a theme from the disk: 283To install/update a plugin or a theme from the disk:
266 284
267``` 285```
268$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --plugin-path /local/plugin/path 286$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run plugin:install -- --plugin-path /local/plugin/path
269``` 287```
270 288
271From NPM: 289From NPM:
272 290
273``` 291```
274$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --npm-name peertube-plugin-myplugin 292$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run plugin:install -- --npm-name peertube-plugin-myplugin
275``` 293```
276 294
277To uninstall a plugin or a theme: 295To uninstall a plugin or a theme:
278 296
279``` 297```
280$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:uninstall -- --npm-name peertube-plugin-myplugin 298$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run plugin:uninstall -- --npm-name peertube-plugin-myplugin
281``` 299```
282 300
283### REPL ([Read Eval Print Loop](https://nodejs.org/docs/latest-v10.x/api/repl.html)) 301### REPL ([Read Eval Print Loop](https://nodejs.org/docs/latest-v10.x/api/repl.html))
diff --git a/tsconfig.json b/tsconfig.json
index 4d2bdd6ba..5ed870c5c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -14,11 +14,15 @@
14 "es2016", 14 "es2016",
15 "es2017" 15 "es2017"
16 ], 16 ],
17 "typeRoots": [ "node_modules/@types", "server/typings" ] 17 "typeRoots": [ "node_modules/@types", "server/typings" ],
18 "baseUrl": "./",
19 "paths": {
20 "@server/typings/*": [ "server/typings/*" ],
21 "@server/models/*": [ "server/models/*" ]
22 }
18 }, 23 },
19 "exclude": [ 24 "exclude": [
20 "server/tools/", 25 "server/tools/",
21 "client/node_modules",
22 "node_modules", 26 "node_modules",
23 "dist", 27 "dist",
24 "storage", 28 "storage",
diff --git a/yarn.lock b/yarn.lock
index f26763845..b5a3a3f47 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -828,24 +828,6 @@ bindings@~1.3.0:
828 resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5" 828 resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5"
829 integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew== 829 integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==
830 830
831bitcore-lib@^0.13.7:
832 version "0.13.19"
833 resolved "https://registry.yarnpkg.com/bitcore-lib/-/bitcore-lib-0.13.19.tgz#48af1e9bda10067c1ab16263472b5add2000f3dc"
834 integrity sha1-SK8em9oQBnwasWJjRyta3SAA89w=
835 dependencies:
836 bn.js "=2.0.4"
837 bs58 "=2.0.0"
838 buffer-compare "=1.0.0"
839 elliptic "=3.0.3"
840 inherits "=2.0.1"
841 lodash "=3.10.1"
842
843"bitcore-message@github:CoMakery/bitcore-message#dist":
844 version "1.0.2"
845 resolved "https://codeload.github.com/CoMakery/bitcore-message/tar.gz/8799cc327029c3d34fc725f05b2cf981363f6ebf"
846 dependencies:
847 bitcore-lib "^0.13.7"
848
849bitfield@^2.0.0: 831bitfield@^2.0.0:
850 version "2.0.0" 832 version "2.0.0"
851 resolved "https://registry.yarnpkg.com/bitfield/-/bitfield-2.0.0.tgz#fbe6767592fe5b4c87ecf1d04126294cc1bfa837" 833 resolved "https://registry.yarnpkg.com/bitfield/-/bitfield-2.0.0.tgz#fbe6767592fe5b4c87ecf1d04126294cc1bfa837"
@@ -968,16 +950,6 @@ bluebird@^3.0.5, bluebird@^3.5.0:
968 resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" 950 resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
969 integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== 951 integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==
970 952
971bn.js@=2.0.4:
972 version "2.0.4"
973 resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.0.4.tgz#220a7cd677f7f1bfa93627ff4193776fe7819480"
974 integrity sha1-Igp81nf38b+pNif/QZN3b+eBlIA=
975
976bn.js@^2.0.0:
977 version "2.2.0"
978 resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.2.0.tgz#12162bc2ae71fc40a5626c33438f3a875cd37625"
979 integrity sha1-EhYrwq5x/EClYmwzQ486h1zTdiU=
980
981bn.js@^4.4.0: 953bn.js@^4.4.0:
982 version "4.11.8" 954 version "4.11.8"
983 resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" 955 resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
@@ -1043,11 +1015,6 @@ braces@^3.0.1:
1043 dependencies: 1015 dependencies:
1044 fill-range "^7.0.1" 1016 fill-range "^7.0.1"
1045 1017
1046brorand@^1.0.1:
1047 version "1.1.0"
1048 resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
1049 integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
1050
1051browser-stdout@1.3.1: 1018browser-stdout@1.3.1:
1052 version "1.3.1" 1019 version "1.3.1"
1053 resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" 1020 resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
@@ -1058,11 +1025,6 @@ browserify-package-json@^1.0.0:
1058 resolved "https://registry.yarnpkg.com/browserify-package-json/-/browserify-package-json-1.0.1.tgz#98dde8aa5c561fd6d3fe49bbaa102b74b396fdea" 1025 resolved "https://registry.yarnpkg.com/browserify-package-json/-/browserify-package-json-1.0.1.tgz#98dde8aa5c561fd6d3fe49bbaa102b74b396fdea"
1059 integrity sha1-mN3oqlxWH9bT/km7qhArdLOW/eo= 1026 integrity sha1-mN3oqlxWH9bT/km7qhArdLOW/eo=
1060 1027
1061bs58@=2.0.0:
1062 version "2.0.0"
1063 resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.0.tgz#72b713bed223a0ac518bbda0e3ce3f4817f39eb5"
1064 integrity sha1-crcTvtIjoKxRi72g484/SBfznrU=
1065
1066buffer-alloc-unsafe@^1.1.0: 1028buffer-alloc-unsafe@^1.1.0:
1067 version "1.1.0" 1029 version "1.1.0"
1068 resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" 1030 resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
@@ -1076,16 +1038,6 @@ buffer-alloc@^1.1.0, buffer-alloc@^1.2.0:
1076 buffer-alloc-unsafe "^1.1.0" 1038 buffer-alloc-unsafe "^1.1.0"
1077 buffer-fill "^1.0.0" 1039 buffer-fill "^1.0.0"
1078 1040
1079buffer-compare@=1.0.0:
1080 version "1.0.0"
1081 resolved "https://registry.yarnpkg.com/buffer-compare/-/buffer-compare-1.0.0.tgz#acaa7a966e98eee9fae14b31c39a5f158fb3c4a2"
1082 integrity sha1-rKp6lm6Y7un64Usxw5pfFY+zxKI=
1083
1084buffer-equal-constant-time@1.0.1:
1085 version "1.0.1"
1086 resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
1087 integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
1088
1089buffer-equals@^1.0.3, buffer-equals@^1.0.4: 1041buffer-equals@^1.0.3, buffer-equals@^1.0.4:
1090 version "1.0.4" 1042 version "1.0.4"
1091 resolved "https://registry.yarnpkg.com/buffer-equals/-/buffer-equals-1.0.4.tgz#0353b54fd07fd9564170671ae6f66b9cf10d27f5" 1043 resolved "https://registry.yarnpkg.com/buffer-equals/-/buffer-equals-1.0.4.tgz#0353b54fd07fd9564170671ae6f66b9cf10d27f5"
@@ -2096,13 +2048,6 @@ ecc-jsbn@~0.1.1:
2096 jsbn "~0.1.0" 2048 jsbn "~0.1.0"
2097 safer-buffer "^2.1.0" 2049 safer-buffer "^2.1.0"
2098 2050
2099ecdsa-sig-formatter@1.0.11:
2100 version "1.0.11"
2101 resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
2102 integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
2103 dependencies:
2104 safe-buffer "^5.0.1"
2105
2106ee-first@1.1.1: 2051ee-first@1.1.1:
2107 version "1.1.1" 2052 version "1.1.1"
2108 resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 2053 resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -2113,16 +2058,6 @@ elegant-spinner@^1.0.1:
2113 resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" 2058 resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
2114 integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= 2059 integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
2115 2060
2116elliptic@=3.0.3:
2117 version "3.0.3"
2118 resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-3.0.3.tgz#865c9b420bfbe55006b9f969f97a0d2c44966595"
2119 integrity sha1-hlybQgv75VAGuflp+XoNLESWZZU=
2120 dependencies:
2121 bn.js "^2.0.0"
2122 brorand "^1.0.1"
2123 hash.js "^1.0.0"
2124 inherits "^2.0.1"
2125
2126emoji-regex@^7.0.1: 2061emoji-regex@^7.0.1:
2127 version "7.0.3" 2062 version "7.0.3"
2128 resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" 2063 resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
@@ -3250,14 +3185,6 @@ has@^1.0.1, has@^1.0.3:
3250 dependencies: 3185 dependencies:
3251 function-bind "^1.1.1" 3186 function-bind "^1.1.1"
3252 3187
3253hash.js@^1.0.0:
3254 version "1.1.7"
3255 resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
3256 integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
3257 dependencies:
3258 inherits "^2.0.3"
3259 minimalistic-assert "^1.0.1"
3260
3261hashish@~0.0.4: 3188hashish@~0.0.4:
3262 version "0.0.4" 3189 version "0.0.4"
3263 resolved "https://registry.yarnpkg.com/hashish/-/hashish-0.0.4.tgz#6d60bc6ffaf711b6afd60e426d077988014e6554" 3190 resolved "https://registry.yarnpkg.com/hashish/-/hashish-0.0.4.tgz#6d60bc6ffaf711b6afd60e426d077988014e6554"
@@ -3481,11 +3408,6 @@ inherits@2.0.3:
3481 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 3408 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
3482 integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 3409 integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
3483 3410
3484inherits@=2.0.1:
3485 version "2.0.1"
3486 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
3487 integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
3488
3489ini@^1.3.4, ini@~1.3.0: 3411ini@^1.3.4, ini@~1.3.0:
3490 version "1.3.5" 3412 version "1.3.5"
3491 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" 3413 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
@@ -4028,25 +3950,6 @@ jsonify@~0.0.0:
4028 resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" 3950 resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
4029 integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= 3951 integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
4030 3952
4031"jsonld-signatures@https://github.com/Chocobozzz/jsonld-signatures#rsa2017":
4032 version "1.2.2-2"
4033 resolved "https://github.com/Chocobozzz/jsonld-signatures#77660963e722eb4541d2d255f9d9d4216329665f"
4034 dependencies:
4035 bitcore-message "github:CoMakery/bitcore-message#dist"
4036 jsonld "^0.5.12"
4037 jws "^3.1.4"
4038 node-forge "^0.7.1"
4039
4040jsonld@^0.5.12:
4041 version "0.5.21"
4042 resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-0.5.21.tgz#4d5b78d717eb92bcd1ac9d88e34efad95370c0bf"
4043 integrity sha512-1dQhaw1Eb3p7Cz5ECE2DNPwLvTmK+f6D45hACBdonJaFKP1bN9zlKLZWbPZQeZtduAc/LNv10J4ML0IiTBVahw==
4044 dependencies:
4045 rdf-canonize "^0.2.1"
4046 request "^2.83.0"
4047 semver "^5.5.0"
4048 xmldom "0.1.19"
4049
4050jsonld@~1.1.0: 3953jsonld@~1.1.0:
4051 version "1.1.0" 3954 version "1.1.0"
4052 resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-1.1.0.tgz#afcb168c44557a7bddead4d4513c3cbcae3bc5b9" 3955 resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-1.1.0.tgz#afcb168c44557a7bddead4d4513c3cbcae3bc5b9"
@@ -4082,23 +3985,6 @@ junk@^3.1.0:
4082 resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" 3985 resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
4083 integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== 3986 integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
4084 3987
4085jwa@^1.4.1:
4086 version "1.4.1"
4087 resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
4088 integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
4089 dependencies:
4090 buffer-equal-constant-time "1.0.1"
4091 ecdsa-sig-formatter "1.0.11"
4092 safe-buffer "^5.0.1"
4093
4094jws@^3.1.4:
4095 version "3.2.2"
4096 resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
4097 integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
4098 dependencies:
4099 jwa "^1.4.1"
4100 safe-buffer "^5.0.1"
4101
4102k-bucket@^4.0.0: 3988k-bucket@^4.0.0:
4103 version "4.0.1" 3989 version "4.0.1"
4104 resolved "https://registry.yarnpkg.com/k-bucket/-/k-bucket-4.0.1.tgz#3fc2e5693f0b7bff90d7b6b476edd6087955d542" 3990 resolved "https://registry.yarnpkg.com/k-bucket/-/k-bucket-4.0.1.tgz#3fc2e5693f0b7bff90d7b6b476edd6087955d542"
@@ -4335,11 +4221,6 @@ lodash@4.17.4:
4335 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 4221 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
4336 integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= 4222 integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=
4337 4223
4338lodash@=3.10.1:
4339 version "3.10.1"
4340 resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
4341 integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=
4342
4343lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.3.0, lodash@~4.17.10: 4224lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.3.0, lodash@~4.17.10:
4344 version "4.17.15" 4225 version "4.17.15"
4345 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" 4226 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
@@ -4638,11 +4519,6 @@ mimic-response@^1.0.0:
4638 resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" 4519 resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
4639 integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== 4520 integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
4640 4521
4641minimalistic-assert@^1.0.1:
4642 version "1.0.1"
4643 resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
4644 integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
4645
4646minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: 4522minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2:
4647 version "3.0.4" 4523 version "3.0.4"
4648 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 4524 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"