diff options
24 files changed, 464 insertions, 234 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c573b12ed..cd9f2036d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -4,7 +4,7 @@ | |||
4 | 4 | ||
5 | ### IMPORTANT NOTES | 5 | ### IMPORTANT NOTES |
6 | 6 | ||
7 | * **/!\ VERY IMPORTANT /!\\** You need to execute manually a script (can be executed after your upgrade, while your PeerTube instance is running) to create HLS video torrents: | 7 | * **/!\ VERY IMPORTANT /!\\** You need to execute manually a script (must be executed after the upgrade and a PeerTube restart, while your instance is running) to create HLS video torrents: |
8 | * `cd /var/www/peertube/peertube-latest && sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production node dist/scripts/migrations/peertube-2.1.js` | 8 | * `cd /var/www/peertube/peertube-latest && sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production node dist/scripts/migrations/peertube-2.1.js` |
9 | * **/!\ VERY IMPORTANT /!\\** In the next PeerTube release (v2.2.0), we'll add a unique index on actors usernames to fix some federation bugs. | 9 | * **/!\ VERY IMPORTANT /!\\** In the next PeerTube release (v2.2.0), we'll add a unique index on actors usernames to fix some federation bugs. |
10 | Please check now if you have conflicts using: | 10 | Please check now if you have conflicts using: |
diff --git a/package.json b/package.json index 0a5484d2a..cec311a18 100644 --- a/package.json +++ b/package.json | |||
@@ -95,7 +95,7 @@ | |||
95 | "deep-object-diff": "^1.1.0", | 95 | "deep-object-diff": "^1.1.0", |
96 | "express": "^4.12.4", | 96 | "express": "^4.12.4", |
97 | "express-oauth-server": "^2.0.0", | 97 | "express-oauth-server": "^2.0.0", |
98 | "express-rate-limit": "^4.0.4", | 98 | "express-rate-limit": "^5.0.0", |
99 | "express-validator": "^6.4.0", | 99 | "express-validator": "^6.4.0", |
100 | "flat": "^5.0.0", | 100 | "flat": "^5.0.0", |
101 | "fluent-ffmpeg": "^2.1.0", | 101 | "fluent-ffmpeg": "^2.1.0", |
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 2812bfe1e..84828e7e0 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | // Intercept ActivityPub client requests | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
3 | import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' | 2 | import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' |
4 | import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' | 3 | import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' |
@@ -37,10 +36,12 @@ import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike' | |||
37 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' | 36 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' |
38 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 37 | import { VideoPlaylistModel } from '../../models/video/video-playlist' |
39 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | 38 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' |
40 | import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption } from '@server/typings/models' | 39 | import { MAccountId, MActorId, MVideoAPWithoutCaption, MVideoId } from '@server/typings/models' |
41 | 40 | ||
42 | const activityPubClientRouter = express.Router() | 41 | const activityPubClientRouter = express.Router() |
43 | 42 | ||
43 | // Intercept ActivityPub client requests | ||
44 | |||
44 | activityPubClientRouter.get('/accounts?/:name', | 45 | activityPubClientRouter.get('/accounts?/:name', |
45 | executeIfActivityPub, | 46 | executeIfActivityPub, |
46 | asyncMiddleware(localAccountValidator), | 47 | asyncMiddleware(localAccountValidator), |
@@ -85,7 +86,7 @@ activityPubClientRouter.get('/videos/watch/:id/activity', | |||
85 | ) | 86 | ) |
86 | activityPubClientRouter.get('/videos/watch/:id/announces', | 87 | activityPubClientRouter.get('/videos/watch/:id/announces', |
87 | executeIfActivityPub, | 88 | executeIfActivityPub, |
88 | asyncMiddleware(videosCustomGetValidator('only-video')), | 89 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
89 | asyncMiddleware(videoAnnouncesController) | 90 | asyncMiddleware(videoAnnouncesController) |
90 | ) | 91 | ) |
91 | activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', | 92 | activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', |
@@ -95,17 +96,17 @@ activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', | |||
95 | ) | 96 | ) |
96 | activityPubClientRouter.get('/videos/watch/:id/likes', | 97 | activityPubClientRouter.get('/videos/watch/:id/likes', |
97 | executeIfActivityPub, | 98 | executeIfActivityPub, |
98 | asyncMiddleware(videosCustomGetValidator('only-video')), | 99 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
99 | asyncMiddleware(videoLikesController) | 100 | asyncMiddleware(videoLikesController) |
100 | ) | 101 | ) |
101 | activityPubClientRouter.get('/videos/watch/:id/dislikes', | 102 | activityPubClientRouter.get('/videos/watch/:id/dislikes', |
102 | executeIfActivityPub, | 103 | executeIfActivityPub, |
103 | asyncMiddleware(videosCustomGetValidator('only-video')), | 104 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
104 | asyncMiddleware(videoDislikesController) | 105 | asyncMiddleware(videoDislikesController) |
105 | ) | 106 | ) |
106 | activityPubClientRouter.get('/videos/watch/:id/comments', | 107 | activityPubClientRouter.get('/videos/watch/:id/comments', |
107 | executeIfActivityPub, | 108 | executeIfActivityPub, |
108 | asyncMiddleware(videosCustomGetValidator('only-video')), | 109 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
109 | asyncMiddleware(videoCommentsController) | 110 | asyncMiddleware(videoCommentsController) |
110 | ) | 111 | ) |
111 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', | 112 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', |
@@ -238,7 +239,7 @@ async function videoAnnounceController (req: express.Request, res: express.Respo | |||
238 | } | 239 | } |
239 | 240 | ||
240 | async function videoAnnouncesController (req: express.Request, res: express.Response) { | 241 | async function videoAnnouncesController (req: express.Request, res: express.Response) { |
241 | const video = res.locals.onlyVideo | 242 | const video = res.locals.onlyImmutableVideo |
242 | 243 | ||
243 | const handler = async (start: number, count: number) => { | 244 | const handler = async (start: number, count: number) => { |
244 | const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) | 245 | const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) |
@@ -253,21 +254,21 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp | |||
253 | } | 254 | } |
254 | 255 | ||
255 | async function videoLikesController (req: express.Request, res: express.Response) { | 256 | async function videoLikesController (req: express.Request, res: express.Response) { |
256 | const video = res.locals.onlyVideo | 257 | const video = res.locals.onlyImmutableVideo |
257 | const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) | 258 | const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) |
258 | 259 | ||
259 | return activityPubResponse(activityPubContextify(json), res) | 260 | return activityPubResponse(activityPubContextify(json), res) |
260 | } | 261 | } |
261 | 262 | ||
262 | async function videoDislikesController (req: express.Request, res: express.Response) { | 263 | async function videoDislikesController (req: express.Request, res: express.Response) { |
263 | const video = res.locals.onlyVideo | 264 | const video = res.locals.onlyImmutableVideo |
264 | const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) | 265 | const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) |
265 | 266 | ||
266 | return activityPubResponse(activityPubContextify(json), res) | 267 | return activityPubResponse(activityPubContextify(json), res) |
267 | } | 268 | } |
268 | 269 | ||
269 | async function videoCommentsController (req: express.Request, res: express.Response) { | 270 | async function videoCommentsController (req: express.Request, res: express.Response) { |
270 | const video = res.locals.onlyVideo | 271 | const video = res.locals.onlyImmutableVideo |
271 | 272 | ||
272 | const handler = async (start: number, count: number) => { | 273 | const handler = async (start: number, count: number) => { |
273 | const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) | 274 | const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) |
@@ -334,10 +335,10 @@ async function videoRedundancyController (req: express.Request, res: express.Res | |||
334 | 335 | ||
335 | if (req.path.endsWith('/activity')) { | 336 | if (req.path.endsWith('/activity')) { |
336 | const data = buildCreateActivity(videoRedundancy.url, serverActor, object, audience) | 337 | const data = buildCreateActivity(videoRedundancy.url, serverActor, object, audience) |
337 | return activityPubResponse(activityPubContextify(data), res) | 338 | return activityPubResponse(activityPubContextify(data, 'CacheFile'), res) |
338 | } | 339 | } |
339 | 340 | ||
340 | return activityPubResponse(activityPubContextify(object), res) | 341 | return activityPubResponse(activityPubContextify(object, 'CacheFile'), res) |
341 | } | 342 | } |
342 | 343 | ||
343 | async function videoPlaylistController (req: express.Request, res: express.Response) { | 344 | async function videoPlaylistController (req: express.Request, res: express.Response) { |
@@ -386,7 +387,7 @@ async function actorPlaylists (req: express.Request, account: MAccountId) { | |||
386 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) | 387 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) |
387 | } | 388 | } |
388 | 389 | ||
389 | function videoRates (req: express.Request, rateType: VideoRateType, video: MVideo, url: string) { | 390 | function videoRates (req: express.Request, rateType: VideoRateType, video: MVideoId, url: string) { |
390 | const handler = async (start: number, count: number) => { | 391 | const handler = async (start: number, count: number) => { |
391 | const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) | 392 | const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) |
392 | return { | 393 | return { |
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts index 6138a32de..7bec6c527 100644 --- a/server/controllers/api/index.ts +++ b/server/controllers/api/index.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as RateLimit from 'express-rate-limit' | ||
3 | import { configRouter } from './config' | 2 | import { configRouter } from './config' |
4 | import { jobsRouter } from './jobs' | 3 | import { jobsRouter } from './jobs' |
5 | import { oauthClientsRouter } from './oauth-clients' | 4 | import { oauthClientsRouter } from './oauth-clients' |
@@ -15,6 +14,7 @@ import { overviewsRouter } from './overviews' | |||
15 | import { videoPlaylistRouter } from './video-playlist' | 14 | import { videoPlaylistRouter } from './video-playlist' |
16 | import { CONFIG } from '../../initializers/config' | 15 | import { CONFIG } from '../../initializers/config' |
17 | import { pluginRouter } from './plugins' | 16 | import { pluginRouter } from './plugins' |
17 | import * as RateLimit from 'express-rate-limit' | ||
18 | 18 | ||
19 | const apiRouter = express.Router() | 19 | const apiRouter = express.Router() |
20 | 20 | ||
@@ -24,8 +24,6 @@ apiRouter.use(cors({ | |||
24 | credentials: true | 24 | credentials: true |
25 | })) | 25 | })) |
26 | 26 | ||
27 | // FIXME: https://github.com/nfriedly/express-rate-limit/issues/138 | ||
28 | // @ts-ignore | ||
29 | const apiRateLimiter = RateLimit({ | 27 | const apiRateLimiter = RateLimit({ |
30 | windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS, | 28 | windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS, |
31 | max: CONFIG.RATES_LIMIT.API.MAX | 29 | max: CONFIG.RATES_LIMIT.API.MAX |
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index b960e80c1..0b7012537 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -53,8 +53,6 @@ import { Hooks } from '@server/lib/plugins/hooks' | |||
53 | 53 | ||
54 | const auditLogger = auditLoggerFactory('users') | 54 | const auditLogger = auditLoggerFactory('users') |
55 | 55 | ||
56 | // FIXME: https://github.com/nfriedly/express-rate-limit/issues/138 | ||
57 | // @ts-ignore | ||
58 | const loginRateLimiter = RateLimit({ | 56 | const loginRateLimiter = RateLimit({ |
59 | windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS, | 57 | windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS, |
60 | max: CONFIG.RATES_LIMIT.LOGIN.MAX | 58 | max: CONFIG.RATES_LIMIT.LOGIN.MAX |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 1d61f8427..eb46ea01f 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -135,7 +135,7 @@ videosRouter.get('/:id', | |||
135 | asyncMiddleware(getVideo) | 135 | asyncMiddleware(getVideo) |
136 | ) | 136 | ) |
137 | videosRouter.post('/:id/views', | 137 | videosRouter.post('/:id/views', |
138 | asyncMiddleware(videosGetValidator), | 138 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
139 | asyncMiddleware(viewVideo) | 139 | asyncMiddleware(viewVideo) |
140 | ) | 140 | ) |
141 | 141 | ||
@@ -458,7 +458,7 @@ async function getVideo (req: express.Request, res: express.Response) { | |||
458 | } | 458 | } |
459 | 459 | ||
460 | async function viewVideo (req: express.Request, res: express.Response) { | 460 | async function viewVideo (req: express.Request, res: express.Response) { |
461 | const videoInstance = res.locals.videoAll | 461 | const videoInstance = res.locals.onlyImmutableVideo |
462 | 462 | ||
463 | const ip = req.ip | 463 | const ip = req.ip |
464 | const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid) | 464 | const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid) |
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 326785b68..2d49e6869 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts | |||
@@ -8,102 +8,117 @@ import { pageToStartAndCount } from './core-utils' | |||
8 | import { URL } from 'url' | 8 | import { URL } from 'url' |
9 | import { MActor, MVideoAccountLight } from '../typings/models' | 9 | import { MActor, MVideoAccountLight } from '../typings/models' |
10 | 10 | ||
11 | export type ContextType = 'All' | 'View' | 'Announce' | 11 | export type ContextType = 'All' | 'View' | 'Announce' | 'CacheFile' |
12 | |||
13 | function getContextData (type: ContextType) { | ||
14 | const context: any[] = [ | ||
15 | 'https://www.w3.org/ns/activitystreams', | ||
16 | 'https://w3id.org/security/v1', | ||
17 | { | ||
18 | RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' | ||
19 | } | ||
20 | ] | ||
12 | 21 | ||
13 | function activityPubContextify <T> (data: T, type: ContextType = 'All') { | 22 | if (type !== 'View' && type !== 'Announce') { |
14 | const base = { | 23 | const additional = { |
15 | RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' | 24 | pt: 'https://joinpeertube.org/ns#', |
25 | sc: 'http://schema.org#' | ||
26 | } | ||
27 | |||
28 | if (type === 'CacheFile') { | ||
29 | Object.assign(additional, { | ||
30 | expires: 'sc:expires', | ||
31 | CacheFile: 'pt:CacheFile' | ||
32 | }) | ||
33 | } else { | ||
34 | Object.assign(additional, { | ||
35 | Hashtag: 'as:Hashtag', | ||
36 | uuid: 'sc:identifier', | ||
37 | category: 'sc:category', | ||
38 | licence: 'sc:license', | ||
39 | subtitleLanguage: 'sc:subtitleLanguage', | ||
40 | sensitive: 'as:sensitive', | ||
41 | language: 'sc:inLanguage', | ||
42 | |||
43 | Infohash: 'pt:Infohash', | ||
44 | originallyPublishedAt: 'sc:datePublished', | ||
45 | views: { | ||
46 | '@type': 'sc:Number', | ||
47 | '@id': 'pt:views' | ||
48 | }, | ||
49 | state: { | ||
50 | '@type': 'sc:Number', | ||
51 | '@id': 'pt:state' | ||
52 | }, | ||
53 | size: { | ||
54 | '@type': 'sc:Number', | ||
55 | '@id': 'pt:size' | ||
56 | }, | ||
57 | fps: { | ||
58 | '@type': 'sc:Number', | ||
59 | '@id': 'pt:fps' | ||
60 | }, | ||
61 | startTimestamp: { | ||
62 | '@type': 'sc:Number', | ||
63 | '@id': 'pt:startTimestamp' | ||
64 | }, | ||
65 | stopTimestamp: { | ||
66 | '@type': 'sc:Number', | ||
67 | '@id': 'pt:stopTimestamp' | ||
68 | }, | ||
69 | position: { | ||
70 | '@type': 'sc:Number', | ||
71 | '@id': 'pt:position' | ||
72 | }, | ||
73 | commentsEnabled: { | ||
74 | '@type': 'sc:Boolean', | ||
75 | '@id': 'pt:commentsEnabled' | ||
76 | }, | ||
77 | downloadEnabled: { | ||
78 | '@type': 'sc:Boolean', | ||
79 | '@id': 'pt:downloadEnabled' | ||
80 | }, | ||
81 | waitTranscoding: { | ||
82 | '@type': 'sc:Boolean', | ||
83 | '@id': 'pt:waitTranscoding' | ||
84 | }, | ||
85 | support: { | ||
86 | '@type': 'sc:Text', | ||
87 | '@id': 'pt:support' | ||
88 | }, | ||
89 | likes: { | ||
90 | '@id': 'as:likes', | ||
91 | '@type': '@id' | ||
92 | }, | ||
93 | dislikes: { | ||
94 | '@id': 'as:dislikes', | ||
95 | '@type': '@id' | ||
96 | }, | ||
97 | playlists: { | ||
98 | '@id': 'pt:playlists', | ||
99 | '@type': '@id' | ||
100 | }, | ||
101 | shares: { | ||
102 | '@id': 'as:shares', | ||
103 | '@type': '@id' | ||
104 | }, | ||
105 | comments: { | ||
106 | '@id': 'as:comments', | ||
107 | '@type': '@id' | ||
108 | } | ||
109 | }) | ||
110 | } | ||
111 | |||
112 | context.push(additional) | ||
16 | } | 113 | } |
17 | 114 | ||
18 | if (type === 'All') { | 115 | return { |
19 | Object.assign(base, { | 116 | '@context': context |
20 | pt: 'https://joinpeertube.org/ns#', | ||
21 | sc: 'http://schema.org#', | ||
22 | Hashtag: 'as:Hashtag', | ||
23 | uuid: 'sc:identifier', | ||
24 | category: 'sc:category', | ||
25 | licence: 'sc:license', | ||
26 | subtitleLanguage: 'sc:subtitleLanguage', | ||
27 | sensitive: 'as:sensitive', | ||
28 | language: 'sc:inLanguage', | ||
29 | expires: 'sc:expires', | ||
30 | CacheFile: 'pt:CacheFile', | ||
31 | Infohash: 'pt:Infohash', | ||
32 | originallyPublishedAt: 'sc:datePublished', | ||
33 | views: { | ||
34 | '@type': 'sc:Number', | ||
35 | '@id': 'pt:views' | ||
36 | }, | ||
37 | state: { | ||
38 | '@type': 'sc:Number', | ||
39 | '@id': 'pt:state' | ||
40 | }, | ||
41 | size: { | ||
42 | '@type': 'sc:Number', | ||
43 | '@id': 'pt:size' | ||
44 | }, | ||
45 | fps: { | ||
46 | '@type': 'sc:Number', | ||
47 | '@id': 'pt:fps' | ||
48 | }, | ||
49 | startTimestamp: { | ||
50 | '@type': 'sc:Number', | ||
51 | '@id': 'pt:startTimestamp' | ||
52 | }, | ||
53 | stopTimestamp: { | ||
54 | '@type': 'sc:Number', | ||
55 | '@id': 'pt:stopTimestamp' | ||
56 | }, | ||
57 | position: { | ||
58 | '@type': 'sc:Number', | ||
59 | '@id': 'pt:position' | ||
60 | }, | ||
61 | commentsEnabled: { | ||
62 | '@type': 'sc:Boolean', | ||
63 | '@id': 'pt:commentsEnabled' | ||
64 | }, | ||
65 | downloadEnabled: { | ||
66 | '@type': 'sc:Boolean', | ||
67 | '@id': 'pt:downloadEnabled' | ||
68 | }, | ||
69 | waitTranscoding: { | ||
70 | '@type': 'sc:Boolean', | ||
71 | '@id': 'pt:waitTranscoding' | ||
72 | }, | ||
73 | support: { | ||
74 | '@type': 'sc:Text', | ||
75 | '@id': 'pt:support' | ||
76 | }, | ||
77 | likes: { | ||
78 | '@id': 'as:likes', | ||
79 | '@type': '@id' | ||
80 | }, | ||
81 | dislikes: { | ||
82 | '@id': 'as:dislikes', | ||
83 | '@type': '@id' | ||
84 | }, | ||
85 | playlists: { | ||
86 | '@id': 'pt:playlists', | ||
87 | '@type': '@id' | ||
88 | }, | ||
89 | shares: { | ||
90 | '@id': 'as:shares', | ||
91 | '@type': '@id' | ||
92 | }, | ||
93 | comments: { | ||
94 | '@id': 'as:comments', | ||
95 | '@type': '@id' | ||
96 | } | ||
97 | }) | ||
98 | } | 117 | } |
118 | } | ||
99 | 119 | ||
100 | return Object.assign({}, data, { | 120 | function activityPubContextify <T> (data: T, type: ContextType = 'All') { |
101 | '@context': [ | 121 | return Object.assign({}, data, getContextData(type)) |
102 | 'https://www.w3.org/ns/activitystreams', | ||
103 | 'https://w3id.org/security/v1', | ||
104 | base | ||
105 | ] | ||
106 | }) | ||
107 | } | 122 | } |
108 | 123 | ||
109 | type ActivityPubCollectionPaginationHandler = (start: number, count: number) => Bluebird<ResultList<any>> | Promise<ResultList<any>> | 124 | type ActivityPubCollectionPaginationHandler = (start: number, count: number) => Bluebird<ResultList<any>> | Promise<ResultList<any>> |
diff --git a/server/helpers/middlewares/videos.ts b/server/helpers/middlewares/videos.ts index 74f529804..409f78650 100644 --- a/server/helpers/middlewares/videos.ts +++ b/server/helpers/middlewares/videos.ts | |||
@@ -2,7 +2,16 @@ import { Response } from 'express' | |||
2 | import { fetchVideo, VideoFetchType } from '../video' | 2 | import { fetchVideo, VideoFetchType } from '../video' |
3 | import { UserRight } from '../../../shared/models/users' | 3 | import { UserRight } from '../../../shared/models/users' |
4 | import { VideoChannelModel } from '../../models/video/video-channel' | 4 | import { VideoChannelModel } from '../../models/video/video-channel' |
5 | import { MUser, MUserAccountId, MVideoAccountLight, MVideoFullLight, MVideoThumbnail, MVideoWithRights } from '@server/typings/models' | 5 | import { |
6 | MUser, | ||
7 | MUserAccountId, | ||
8 | MVideoAccountLight, | ||
9 | MVideoFullLight, | ||
10 | MVideoIdThumbnail, | ||
11 | MVideoImmutable, | ||
12 | MVideoThumbnail, | ||
13 | MVideoWithRights | ||
14 | } from '@server/typings/models' | ||
6 | 15 | ||
7 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { | 16 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { |
8 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined | 17 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined |
@@ -22,8 +31,12 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi | |||
22 | res.locals.videoAll = video as MVideoFullLight | 31 | res.locals.videoAll = video as MVideoFullLight |
23 | break | 32 | break |
24 | 33 | ||
34 | case 'only-immutable-attributes': | ||
35 | res.locals.onlyImmutableVideo = video as MVideoImmutable | ||
36 | break | ||
37 | |||
25 | case 'id': | 38 | case 'id': |
26 | res.locals.videoId = video | 39 | res.locals.videoId = video as MVideoIdThumbnail |
27 | break | 40 | break |
28 | 41 | ||
29 | case 'only-video': | 42 | case 'only-video': |
diff --git a/server/helpers/video.ts b/server/helpers/video.ts index 5b9c026b1..4fe2a60f0 100644 --- a/server/helpers/video.ts +++ b/server/helpers/video.ts | |||
@@ -5,13 +5,15 @@ import { | |||
5 | MVideoFullLight, | 5 | MVideoFullLight, |
6 | MVideoIdThumbnail, | 6 | MVideoIdThumbnail, |
7 | MVideoThumbnail, | 7 | MVideoThumbnail, |
8 | MVideoWithRights | 8 | MVideoWithRights, |
9 | MVideoImmutable | ||
9 | } from '@server/typings/models' | 10 | } from '@server/typings/models' |
10 | import { Response } from 'express' | 11 | import { Response } from 'express' |
11 | 12 | ||
12 | type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' | 13 | type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' | 'only-immutable-attributes' |
13 | 14 | ||
14 | function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight> | 15 | function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight> |
16 | function fetchVideo (id: number | string, fetchType: 'only-immutable-attributes'): Bluebird<MVideoImmutable> | ||
15 | function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail> | 17 | function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail> |
16 | function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights> | 18 | function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights> |
17 | function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail> | 19 | function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail> |
@@ -19,14 +21,16 @@ function fetchVideo ( | |||
19 | id: number | string, | 21 | id: number | string, |
20 | fetchType: VideoFetchType, | 22 | fetchType: VideoFetchType, |
21 | userId?: number | 23 | userId?: number |
22 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> | 24 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail | MVideoImmutable> |
23 | function fetchVideo ( | 25 | function fetchVideo ( |
24 | id: number | string, | 26 | id: number | string, |
25 | fetchType: VideoFetchType, | 27 | fetchType: VideoFetchType, |
26 | userId?: number | 28 | userId?: number |
27 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> { | 29 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail | MVideoImmutable> { |
28 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) | 30 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) |
29 | 31 | ||
32 | if (fetchType === 'only-immutable-attributes') return VideoModel.loadImmutableAttributes(id) | ||
33 | |||
30 | if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) | 34 | if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) |
31 | 35 | ||
32 | if (fetchType === 'only-video') return VideoModel.load(id) | 36 | if (fetchType === 'only-video') return VideoModel.load(id) |
@@ -34,14 +38,23 @@ function fetchVideo ( | |||
34 | if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id) | 38 | if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id) |
35 | } | 39 | } |
36 | 40 | ||
37 | type VideoFetchByUrlType = 'all' | 'only-video' | 41 | type VideoFetchByUrlType = 'all' | 'only-video' | 'only-immutable-attributes' |
38 | 42 | ||
39 | function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountLightBlacklistAllFiles> | 43 | function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountLightBlacklistAllFiles> |
44 | function fetchVideoByUrl (url: string, fetchType: 'only-immutable-attributes'): Bluebird<MVideoImmutable> | ||
40 | function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail> | 45 | function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail> |
41 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> | 46 | function fetchVideoByUrl ( |
42 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> { | 47 | url: string, |
48 | fetchType: VideoFetchByUrlType | ||
49 | ): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> | ||
50 | function fetchVideoByUrl ( | ||
51 | url: string, | ||
52 | fetchType: VideoFetchByUrlType | ||
53 | ): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> { | ||
43 | if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url) | 54 | if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url) |
44 | 55 | ||
56 | if (fetchType === 'only-immutable-attributes') return VideoModel.loadByUrlImmutableAttributes(url) | ||
57 | |||
45 | if (fetchType === 'only-video') return VideoModel.loadByUrl(url) | 58 | if (fetchType === 'only-video') return VideoModel.loadByUrl(url) |
46 | } | 59 | } |
47 | 60 | ||
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts index f2ab54cf7..9933ae2b5 100644 --- a/server/lib/activitypub/audience.ts +++ b/server/lib/activitypub/audience.ts | |||
@@ -4,11 +4,11 @@ import { ACTIVITY_PUB } from '../../initializers/constants' | |||
4 | import { ActorModel } from '../../models/activitypub/actor' | 4 | import { ActorModel } from '../../models/activitypub/actor' |
5 | import { VideoModel } from '../../models/video/video' | 5 | import { VideoModel } from '../../models/video/video' |
6 | import { VideoShareModel } from '../../models/video/video-share' | 6 | import { VideoShareModel } from '../../models/video/video-share' |
7 | import { MActorFollowersUrl, MActorLight, MCommentOwner, MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../../typings/models' | 7 | import { MActorFollowersUrl, MActorLight, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '../../typings/models' |
8 | 8 | ||
9 | function getRemoteVideoAudience (video: MVideoAccountLight, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience { | 9 | function getRemoteVideoAudience (accountActor: MActorUrl, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience { |
10 | return { | 10 | return { |
11 | to: [ video.VideoChannel.Account.Actor.url ], | 11 | to: [ accountActor.url ], |
12 | cc: actorsInvolvedInVideo.map(a => a.followersUrl) | 12 | cc: actorsInvolvedInVideo.map(a => a.followersUrl) |
13 | } | 13 | } |
14 | } | 14 | } |
@@ -48,7 +48,7 @@ function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[ | |||
48 | } | 48 | } |
49 | } | 49 | } |
50 | 50 | ||
51 | async function getActorsInvolvedInVideo (video: MVideo, t: Transaction) { | 51 | async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) { |
52 | const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t) | 52 | const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t) |
53 | 53 | ||
54 | const videoAll = video as VideoModel | 54 | const videoAll = video as VideoModel |
diff --git a/server/lib/activitypub/process/process-view.ts b/server/lib/activitypub/process/process-view.ts index df29ee968..b3b6c933d 100644 --- a/server/lib/activitypub/process/process-view.ts +++ b/server/lib/activitypub/process/process-view.ts | |||
@@ -23,7 +23,8 @@ async function processCreateView (activity: ActivityView | ActivityCreate, byAct | |||
23 | 23 | ||
24 | const options = { | 24 | const options = { |
25 | videoObject, | 25 | videoObject, |
26 | fetchType: 'only-video' as 'only-video' | 26 | fetchType: 'only-immutable-attributes' as 'only-immutable-attributes', |
27 | allowRefresh: false as false | ||
27 | } | 28 | } |
28 | const { video } = await getOrCreateVideoAndAccountAndChannel(options) | 29 | const { video } = await getOrCreateVideoAndAccountAndChannel(options) |
29 | 30 | ||
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts index 3585d704a..8bdcf6417 100644 --- a/server/lib/activitypub/send/send-create.ts +++ b/server/lib/activitypub/send/send-create.ts | |||
@@ -16,6 +16,7 @@ import { | |||
16 | MVideoRedundancyFileVideo, | 16 | MVideoRedundancyFileVideo, |
17 | MVideoRedundancyStreamingPlaylistVideo | 17 | MVideoRedundancyStreamingPlaylistVideo |
18 | } from '../../../typings/models' | 18 | } from '../../../typings/models' |
19 | import { ContextType } from '@server/helpers/activitypub' | ||
19 | 20 | ||
20 | async function sendCreateVideo (video: MVideoAP, t: Transaction) { | 21 | async function sendCreateVideo (video: MVideoAP, t: Transaction) { |
21 | if (!video.hasPrivacyForFederation()) return undefined | 22 | if (!video.hasPrivacyForFederation()) return undefined |
@@ -42,7 +43,8 @@ async function sendCreateCacheFile ( | |||
42 | byActor, | 43 | byActor, |
43 | video, | 44 | video, |
44 | url: fileRedundancy.url, | 45 | url: fileRedundancy.url, |
45 | object: fileRedundancy.toActivityPubObject() | 46 | object: fileRedundancy.toActivityPubObject(), |
47 | contextType: 'CacheFile' | ||
46 | }) | 48 | }) |
47 | } | 49 | } |
48 | 50 | ||
@@ -135,6 +137,7 @@ async function sendVideoRelatedCreateActivity (options: { | |||
135 | url: string | 137 | url: string |
136 | object: any | 138 | object: any |
137 | transaction?: Transaction | 139 | transaction?: Transaction |
140 | contextType?: ContextType | ||
138 | }) { | 141 | }) { |
139 | const activityBuilder = (audience: ActivityAudience) => { | 142 | const activityBuilder = (audience: ActivityAudience) => { |
140 | return buildCreateActivity(options.url, options.byActor, options.object, audience) | 143 | return buildCreateActivity(options.url, options.byActor, options.object, audience) |
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index cb500bd34..2b01ca5e7 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts | |||
@@ -84,7 +84,7 @@ async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVide | |||
84 | return buildUpdateActivity(url, byActor, redundancyObject, audience) | 84 | return buildUpdateActivity(url, byActor, redundancyObject, audience) |
85 | } | 85 | } |
86 | 86 | ||
87 | return sendVideoRelatedActivity(activityBuilder, { byActor, video }) | 87 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' }) |
88 | } | 88 | } |
89 | 89 | ||
90 | async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { | 90 | async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { |
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts index 47482b9a9..1f864ea52 100644 --- a/server/lib/activitypub/send/send-view.ts +++ b/server/lib/activitypub/send/send-view.ts | |||
@@ -5,9 +5,9 @@ import { getVideoLikeActivityPubUrl } from '../url' | |||
5 | import { sendVideoRelatedActivity } from './utils' | 5 | import { sendVideoRelatedActivity } from './utils' |
6 | import { audiencify, getAudience } from '../audience' | 6 | import { audiencify, getAudience } from '../audience' |
7 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { MActorAudience, MVideoAccountLight, MVideoUrl } from '@server/typings/models' | 8 | import { MActorAudience, MVideoImmutable, MVideoUrl } from '@server/typings/models' |
9 | 9 | ||
10 | async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Transaction) { | 10 | async function sendView (byActor: ActorModel, video: MVideoImmutable, t: Transaction) { |
11 | logger.info('Creating job to send view of %s.', video.url) | 11 | logger.info('Creating job to send view of %s.', video.url) |
12 | 12 | ||
13 | const activityBuilder = (audience: ActivityAudience) => { | 13 | const activityBuilder = (audience: ActivityAudience) => { |
diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts index 0d67bb3d6..b57bae8fd 100644 --- a/server/lib/activitypub/send/utils.ts +++ b/server/lib/activitypub/send/utils.ts | |||
@@ -7,12 +7,12 @@ import { JobQueue } from '../../job-queue' | |||
7 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' | 7 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' |
8 | import { getServerActor } from '../../../helpers/utils' | 8 | import { getServerActor } from '../../../helpers/utils' |
9 | import { afterCommitIfTransaction } from '../../../helpers/database-utils' | 9 | import { afterCommitIfTransaction } from '../../../helpers/database-utils' |
10 | import { MActorWithInboxes, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models' | 10 | import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../typings/models' |
11 | import { ContextType } from '@server/helpers/activitypub' | 11 | import { ContextType } from '@server/helpers/activitypub' |
12 | 12 | ||
13 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { | 13 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { |
14 | byActor: MActorLight | 14 | byActor: MActorLight |
15 | video: MVideoAccountLight | 15 | video: MVideoImmutable | MVideoAccountLight |
16 | transaction?: Transaction | 16 | transaction?: Transaction |
17 | contextType?: ContextType | 17 | contextType?: ContextType |
18 | }) { | 18 | }) { |
@@ -22,11 +22,13 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud | |||
22 | 22 | ||
23 | // Send to origin | 23 | // Send to origin |
24 | if (video.isOwned() === false) { | 24 | if (video.isOwned() === false) { |
25 | const audience = getRemoteVideoAudience(video, actorsInvolvedInVideo) | 25 | const accountActor = (video as MVideoAccountLight).VideoChannel?.Account?.Actor || await ActorModel.loadAccountActorByVideoId(video.id) |
26 | |||
27 | const audience = getRemoteVideoAudience(accountActor, actorsInvolvedInVideo) | ||
26 | const activity = activityBuilder(audience) | 28 | const activity = activityBuilder(audience) |
27 | 29 | ||
28 | return afterCommitIfTransaction(transaction, () => { | 30 | return afterCommitIfTransaction(transaction, () => { |
29 | return unicastTo(activity, byActor, video.VideoChannel.Account.Actor.getSharedInbox(), contextType) | 31 | return unicastTo(activity, byActor, accountActor.getSharedInbox(), contextType) |
30 | }) | 32 | }) |
31 | } | 33 | } |
32 | 34 | ||
@@ -43,7 +45,7 @@ async function forwardVideoRelatedActivity ( | |||
43 | activity: Activity, | 45 | activity: Activity, |
44 | t: Transaction, | 46 | t: Transaction, |
45 | followersException: MActorWithInboxes[] = [], | 47 | followersException: MActorWithInboxes[] = [], |
46 | video: MVideo | 48 | video: MVideoId |
47 | ) { | 49 | ) { |
48 | // Mastodon does not add our announces in audience, so we forward to them manually | 50 | // Mastodon does not add our announces in audience, so we forward to them manually |
49 | const additionalActors = await getActorsInvolvedInVideo(video, t) | 51 | const additionalActors = await getActorsInvolvedInVideo(video, t) |
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 9e43caa20..7d8296e45 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -68,7 +68,7 @@ import { | |||
68 | MVideoAPWithoutCaption, | 68 | MVideoAPWithoutCaption, |
69 | MVideoFile, | 69 | MVideoFile, |
70 | MVideoFullLight, | 70 | MVideoFullLight, |
71 | MVideoId, | 71 | MVideoId, MVideoImmutable, |
72 | MVideoThumbnail | 72 | MVideoThumbnail |
73 | } from '../../typings/models' | 73 | } from '../../typings/models' |
74 | import { MThumbnail } from '../../typings/models/video/thumbnail' | 74 | import { MThumbnail } from '../../typings/models/video/thumbnail' |
@@ -200,24 +200,41 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo | |||
200 | await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload })) | 200 | await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload })) |
201 | } | 201 | } |
202 | 202 | ||
203 | function getOrCreateVideoAndAccountAndChannel (options: { | 203 | type GetVideoResult <T> = Promise<{ |
204 | video: T | ||
205 | created: boolean | ||
206 | autoBlacklisted?: boolean | ||
207 | }> | ||
208 | |||
209 | type GetVideoParamAll = { | ||
204 | videoObject: { id: string } | string | 210 | videoObject: { id: string } | string |
205 | syncParam?: SyncParam | 211 | syncParam?: SyncParam |
206 | fetchType?: 'all' | 212 | fetchType?: 'all' |
207 | allowRefresh?: boolean | 213 | allowRefresh?: boolean |
208 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles, created: boolean, autoBlacklisted?: boolean }> | 214 | } |
209 | function getOrCreateVideoAndAccountAndChannel (options: { | 215 | |
216 | type GetVideoParamImmutable = { | ||
210 | videoObject: { id: string } | string | 217 | videoObject: { id: string } | string |
211 | syncParam?: SyncParam | 218 | syncParam?: SyncParam |
212 | fetchType?: VideoFetchByUrlType | 219 | fetchType: 'only-immutable-attributes' |
213 | allowRefresh?: boolean | 220 | allowRefresh: false |
214 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> | 221 | } |
215 | async function getOrCreateVideoAndAccountAndChannel (options: { | 222 | |
223 | type GetVideoParamOther = { | ||
216 | videoObject: { id: string } | string | 224 | videoObject: { id: string } | string |
217 | syncParam?: SyncParam | 225 | syncParam?: SyncParam |
218 | fetchType?: VideoFetchByUrlType | 226 | fetchType?: 'all' | 'only-video' |
219 | allowRefresh?: boolean // true by default | 227 | allowRefresh?: boolean |
220 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> { | 228 | } |
229 | |||
230 | function getOrCreateVideoAndAccountAndChannel (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles> | ||
231 | function getOrCreateVideoAndAccountAndChannel (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable> | ||
232 | function getOrCreateVideoAndAccountAndChannel ( | ||
233 | options: GetVideoParamOther | ||
234 | ): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> | ||
235 | async function getOrCreateVideoAndAccountAndChannel ( | ||
236 | options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther | ||
237 | ): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> { | ||
221 | // Default params | 238 | // Default params |
222 | const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } | 239 | const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } |
223 | const fetchType = options.fetchType || 'all' | 240 | const fetchType = options.fetchType || 'all' |
@@ -225,12 +242,13 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
225 | 242 | ||
226 | // Get video url | 243 | // Get video url |
227 | const videoUrl = getAPId(options.videoObject) | 244 | const videoUrl = getAPId(options.videoObject) |
228 | |||
229 | let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType) | 245 | let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType) |
246 | |||
230 | if (videoFromDatabase) { | 247 | if (videoFromDatabase) { |
231 | if (videoFromDatabase.isOutdated() && allowRefresh === true) { | 248 | // If allowRefresh is true, we could not call this function using 'only-immutable-attributes' fetch type |
249 | if (allowRefresh === true && (videoFromDatabase as MVideoThumbnail).isOutdated()) { | ||
232 | const refreshOptions = { | 250 | const refreshOptions = { |
233 | video: videoFromDatabase, | 251 | video: videoFromDatabase as MVideoThumbnail, |
234 | fetchedType: fetchType, | 252 | fetchedType: fetchType, |
235 | syncParam | 253 | syncParam |
236 | } | 254 | } |
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index 11dd02706..a027c4840 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -147,7 +147,10 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R | |||
147 | }) | 147 | }) |
148 | } | 148 | } |
149 | 149 | ||
150 | const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video-with-rights', authenticateInQuery = false) => { | 150 | const videosCustomGetValidator = ( |
151 | fetchType: 'all' | 'only-video' | 'only-video-with-rights' | 'only-immutable-attributes', | ||
152 | authenticateInQuery = false | ||
153 | ) => { | ||
151 | return [ | 154 | return [ |
152 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 155 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
153 | 156 | ||
@@ -157,6 +160,9 @@ const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video- | |||
157 | if (areValidationErrors(req, res)) return | 160 | if (areValidationErrors(req, res)) return |
158 | if (!await doesVideoExist(req.params.id, res, fetchType)) return | 161 | if (!await doesVideoExist(req.params.id, res, fetchType)) return |
159 | 162 | ||
163 | // Controllers does not need to check video rights | ||
164 | if (fetchType === 'only-immutable-attributes') return next() | ||
165 | |||
160 | const video = getVideoWithAttributes(res) | 166 | const video = getVideoWithAttributes(res) |
161 | const videoAll = video as MVideoFullLight | 167 | const videoAll = video as MVideoFullLight |
162 | 168 | ||
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 0905a0fb2..a0081f259 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -32,8 +32,9 @@ import { FindOptions, IncludeOptions, Op, Transaction, WhereOptions } from 'sequ | |||
32 | import { AccountBlocklistModel } from './account-blocklist' | 32 | import { AccountBlocklistModel } from './account-blocklist' |
33 | import { ServerBlocklistModel } from '../server/server-blocklist' | 33 | import { ServerBlocklistModel } from '../server/server-blocklist' |
34 | import { ActorFollowModel } from '../activitypub/actor-follow' | 34 | import { ActorFollowModel } from '../activitypub/actor-follow' |
35 | import { MAccountActor, MAccountDefault, MAccountSummaryFormattable, MAccountFormattable, MAccountAP } from '../../typings/models' | 35 | import { MAccountActor, MAccountAP, MAccountDefault, MAccountFormattable, MAccountSummaryFormattable } from '../../typings/models' |
36 | import * as Bluebird from 'bluebird' | 36 | import * as Bluebird from 'bluebird' |
37 | import { ModelCache } from '@server/models/model-cache' | ||
37 | 38 | ||
38 | export enum ScopeNames { | 39 | export enum ScopeNames { |
39 | SUMMARY = 'SUMMARY' | 40 | SUMMARY = 'SUMMARY' |
@@ -218,8 +219,6 @@ export class AccountModel extends Model<AccountModel> { | |||
218 | }) | 219 | }) |
219 | BlockedAccounts: AccountBlocklistModel[] | 220 | BlockedAccounts: AccountBlocklistModel[] |
220 | 221 | ||
221 | private static cache: { [ id: string ]: any } = {} | ||
222 | |||
223 | @BeforeDestroy | 222 | @BeforeDestroy |
224 | static async sendDeleteIfOwned (instance: AccountModel, options) { | 223 | static async sendDeleteIfOwned (instance: AccountModel, options) { |
225 | if (!instance.Actor) { | 224 | if (!instance.Actor) { |
@@ -247,45 +246,43 @@ export class AccountModel extends Model<AccountModel> { | |||
247 | } | 246 | } |
248 | 247 | ||
249 | static loadLocalByName (name: string): Bluebird<MAccountDefault> { | 248 | static loadLocalByName (name: string): Bluebird<MAccountDefault> { |
250 | // The server actor never change, so we can easily cache it | 249 | const fun = () => { |
251 | if (name === SERVER_ACTOR_NAME && AccountModel.cache[name]) { | 250 | const query = { |
252 | return Bluebird.resolve(AccountModel.cache[name]) | 251 | where: { |
253 | } | 252 | [Op.or]: [ |
254 | 253 | { | |
255 | const query = { | 254 | userId: { |
256 | where: { | 255 | [Op.ne]: null |
257 | [Op.or]: [ | 256 | } |
258 | { | 257 | }, |
259 | userId: { | 258 | { |
260 | [Op.ne]: null | 259 | applicationId: { |
260 | [Op.ne]: null | ||
261 | } | ||
261 | } | 262 | } |
262 | }, | 263 | ] |
264 | }, | ||
265 | include: [ | ||
263 | { | 266 | { |
264 | applicationId: { | 267 | model: ActorModel, |
265 | [Op.ne]: null | 268 | required: true, |
269 | where: { | ||
270 | preferredUsername: name | ||
266 | } | 271 | } |
267 | } | 272 | } |
268 | ] | 273 | ] |
269 | }, | 274 | } |
270 | include: [ | ||
271 | { | ||
272 | model: ActorModel, | ||
273 | required: true, | ||
274 | where: { | ||
275 | preferredUsername: name | ||
276 | } | ||
277 | } | ||
278 | ] | ||
279 | } | ||
280 | 275 | ||
281 | return AccountModel.findOne(query) | 276 | return AccountModel.findOne(query) |
282 | .then(account => { | 277 | } |
283 | if (name === SERVER_ACTOR_NAME) { | ||
284 | AccountModel.cache[name] = account | ||
285 | } | ||
286 | 278 | ||
287 | return account | 279 | return ModelCache.Instance.doCache({ |
288 | }) | 280 | cacheType: 'local-account-name', |
281 | key: name, | ||
282 | fun, | ||
283 | // The server actor never change, so we can easily cache it | ||
284 | whitelist: () => name === SERVER_ACTOR_NAME | ||
285 | }) | ||
289 | } | 286 | } |
290 | 287 | ||
291 | static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> { | 288 | static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> { |
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 00e8dc954..e547d2c0c 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts | |||
@@ -48,6 +48,7 @@ import { | |||
48 | } from '../../typings/models' | 48 | } from '../../typings/models' |
49 | import * as Bluebird from 'bluebird' | 49 | import * as Bluebird from 'bluebird' |
50 | import { Op, Transaction, literal } from 'sequelize' | 50 | import { Op, Transaction, literal } from 'sequelize' |
51 | import { ModelCache } from '@server/models/model-cache' | ||
51 | 52 | ||
52 | enum ScopeNames { | 53 | enum ScopeNames { |
53 | FULL = 'FULL' | 54 | FULL = 'FULL' |
@@ -276,9 +277,6 @@ export class ActorModel extends Model<ActorModel> { | |||
276 | }) | 277 | }) |
277 | VideoChannel: VideoChannelModel | 278 | VideoChannel: VideoChannelModel |
278 | 279 | ||
279 | private static localNameCache: { [ id: string ]: any } = {} | ||
280 | private static localUrlCache: { [ id: string ]: any } = {} | ||
281 | |||
282 | static load (id: number): Bluebird<MActor> { | 280 | static load (id: number): Bluebird<MActor> { |
283 | return ActorModel.unscoped().findByPk(id) | 281 | return ActorModel.unscoped().findByPk(id) |
284 | } | 282 | } |
@@ -345,54 +343,50 @@ export class ActorModel extends Model<ActorModel> { | |||
345 | } | 343 | } |
346 | 344 | ||
347 | static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorFull> { | 345 | static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorFull> { |
348 | // The server actor never change, so we can easily cache it | 346 | const fun = () => { |
349 | if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.localNameCache[preferredUsername]) { | 347 | const query = { |
350 | return Bluebird.resolve(ActorModel.localNameCache[preferredUsername]) | 348 | where: { |
351 | } | 349 | preferredUsername, |
350 | serverId: null | ||
351 | }, | ||
352 | transaction | ||
353 | } | ||
352 | 354 | ||
353 | const query = { | 355 | return ActorModel.scope(ScopeNames.FULL) |
354 | where: { | 356 | .findOne(query) |
355 | preferredUsername, | ||
356 | serverId: null | ||
357 | }, | ||
358 | transaction | ||
359 | } | 357 | } |
360 | 358 | ||
361 | return ActorModel.scope(ScopeNames.FULL) | 359 | return ModelCache.Instance.doCache({ |
362 | .findOne(query) | 360 | cacheType: 'local-actor-name', |
363 | .then(actor => { | 361 | key: preferredUsername, |
364 | if (preferredUsername === SERVER_ACTOR_NAME) { | 362 | // The server actor never change, so we can easily cache it |
365 | ActorModel.localNameCache[preferredUsername] = actor | 363 | whitelist: () => preferredUsername === SERVER_ACTOR_NAME, |
366 | } | 364 | fun |
367 | 365 | }) | |
368 | return actor | ||
369 | }) | ||
370 | } | 366 | } |
371 | 367 | ||
372 | static loadLocalUrlByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorUrl> { | 368 | static loadLocalUrlByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorUrl> { |
373 | // The server actor never change, so we can easily cache it | 369 | const fun = () => { |
374 | if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.localUrlCache[preferredUsername]) { | 370 | const query = { |
375 | return Bluebird.resolve(ActorModel.localUrlCache[preferredUsername]) | 371 | attributes: [ 'url' ], |
376 | } | 372 | where: { |
373 | preferredUsername, | ||
374 | serverId: null | ||
375 | }, | ||
376 | transaction | ||
377 | } | ||
377 | 378 | ||
378 | const query = { | 379 | return ActorModel.unscoped() |
379 | attributes: [ 'url' ], | 380 | .findOne(query) |
380 | where: { | ||
381 | preferredUsername, | ||
382 | serverId: null | ||
383 | }, | ||
384 | transaction | ||
385 | } | 381 | } |
386 | 382 | ||
387 | return ActorModel.unscoped() | 383 | return ModelCache.Instance.doCache({ |
388 | .findOne(query) | 384 | cacheType: 'local-actor-name', |
389 | .then(actor => { | 385 | key: preferredUsername, |
390 | if (preferredUsername === SERVER_ACTOR_NAME) { | 386 | // The server actor never change, so we can easily cache it |
391 | ActorModel.localUrlCache[preferredUsername] = actor | 387 | whitelist: () => preferredUsername === SERVER_ACTOR_NAME, |
392 | } | 388 | fun |
393 | 389 | }) | |
394 | return actor | ||
395 | }) | ||
396 | } | 390 | } |
397 | 391 | ||
398 | static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> { | 392 | static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> { |
@@ -468,6 +462,36 @@ export class ActorModel extends Model<ActorModel> { | |||
468 | }, { where, transaction }) | 462 | }, { where, transaction }) |
469 | } | 463 | } |
470 | 464 | ||
465 | static loadAccountActorByVideoId (videoId: number): Bluebird<MActor> { | ||
466 | const query = { | ||
467 | include: [ | ||
468 | { | ||
469 | attributes: [ 'id' ], | ||
470 | model: AccountModel.unscoped(), | ||
471 | required: true, | ||
472 | include: [ | ||
473 | { | ||
474 | attributes: [ 'id', 'accountId' ], | ||
475 | model: VideoChannelModel.unscoped(), | ||
476 | required: true, | ||
477 | include: [ | ||
478 | { | ||
479 | attributes: [ 'id', 'channelId' ], | ||
480 | model: VideoModel.unscoped(), | ||
481 | where: { | ||
482 | id: videoId | ||
483 | } | ||
484 | } | ||
485 | ] | ||
486 | } | ||
487 | ] | ||
488 | } | ||
489 | ] | ||
490 | } | ||
491 | |||
492 | return ActorModel.unscoped().findOne(query) | ||
493 | } | ||
494 | |||
471 | getSharedInbox (this: MActorWithInboxes) { | 495 | getSharedInbox (this: MActorWithInboxes) { |
472 | return this.sharedInboxUrl || this.inboxUrl | 496 | return this.sharedInboxUrl || this.inboxUrl |
473 | } | 497 | } |
diff --git a/server/models/model-cache.ts b/server/models/model-cache.ts new file mode 100644 index 000000000..a87f99aa2 --- /dev/null +++ b/server/models/model-cache.ts | |||
@@ -0,0 +1,91 @@ | |||
1 | import { Model } from 'sequelize-typescript' | ||
2 | import * as Bluebird from 'bluebird' | ||
3 | import { logger } from '@server/helpers/logger' | ||
4 | |||
5 | type ModelCacheType = | ||
6 | 'local-account-name' | ||
7 | | 'local-actor-name' | ||
8 | | 'local-actor-url' | ||
9 | | 'load-video-immutable-id' | ||
10 | | 'load-video-immutable-url' | ||
11 | |||
12 | type DeleteKey = | ||
13 | 'video' | ||
14 | |||
15 | class ModelCache { | ||
16 | |||
17 | private static instance: ModelCache | ||
18 | |||
19 | private readonly localCache: { [id in ModelCacheType]: Map<string, any> } = { | ||
20 | 'local-account-name': new Map(), | ||
21 | 'local-actor-name': new Map(), | ||
22 | 'local-actor-url': new Map(), | ||
23 | 'load-video-immutable-id': new Map(), | ||
24 | 'load-video-immutable-url': new Map() | ||
25 | } | ||
26 | |||
27 | private readonly deleteIds: { | ||
28 | [deleteKey in DeleteKey]: Map<number, { cacheType: ModelCacheType, key: string }[]> | ||
29 | } = { | ||
30 | video: new Map() | ||
31 | } | ||
32 | |||
33 | private constructor () { | ||
34 | } | ||
35 | |||
36 | static get Instance () { | ||
37 | return this.instance || (this.instance = new this()) | ||
38 | } | ||
39 | |||
40 | doCache<T extends Model> (options: { | ||
41 | cacheType: ModelCacheType | ||
42 | key: string | ||
43 | fun: () => Bluebird<T> | ||
44 | whitelist?: () => boolean | ||
45 | deleteKey?: DeleteKey | ||
46 | }) { | ||
47 | const { cacheType, key, fun, whitelist, deleteKey } = options | ||
48 | |||
49 | if (whitelist && whitelist() !== true) return fun() | ||
50 | |||
51 | const cache = this.localCache[cacheType] | ||
52 | |||
53 | if (cache.has(key)) { | ||
54 | logger.debug('Model cache hit for %s -> %s.', cacheType, key) | ||
55 | return Bluebird.resolve<T>(cache.get(key)) | ||
56 | } | ||
57 | |||
58 | return fun().then(m => { | ||
59 | if (!m) return m | ||
60 | |||
61 | if (!whitelist || whitelist()) cache.set(key, m) | ||
62 | |||
63 | if (deleteKey) { | ||
64 | const map = this.deleteIds[deleteKey] | ||
65 | if (!map.has(m.id)) map.set(m.id, []) | ||
66 | |||
67 | const a = map.get(m.id) | ||
68 | a.push({ cacheType, key }) | ||
69 | } | ||
70 | |||
71 | return m | ||
72 | }) | ||
73 | } | ||
74 | |||
75 | invalidateCache (deleteKey: DeleteKey, modelId: number) { | ||
76 | const map = this.deleteIds[deleteKey] | ||
77 | |||
78 | if (!map.has(modelId)) return | ||
79 | |||
80 | for (const toDelete of map.get(modelId)) { | ||
81 | logger.debug('Removing %s -> %d of model cache %s -> %s.', deleteKey, modelId, toDelete.cacheType, toDelete.key) | ||
82 | this.localCache[toDelete.cacheType].delete(toDelete.key) | ||
83 | } | ||
84 | |||
85 | map.delete(modelId) | ||
86 | } | ||
87 | } | ||
88 | |||
89 | export { | ||
90 | ModelCache | ||
91 | } | ||
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 1ec8d717e..5964526a9 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -120,7 +120,7 @@ import { | |||
120 | MVideoFormattableDetails, | 120 | MVideoFormattableDetails, |
121 | MVideoForUser, | 121 | MVideoForUser, |
122 | MVideoFullLight, | 122 | MVideoFullLight, |
123 | MVideoIdThumbnail, | 123 | MVideoIdThumbnail, MVideoImmutable, |
124 | MVideoThumbnail, | 124 | MVideoThumbnail, |
125 | MVideoThumbnailBlacklist, | 125 | MVideoThumbnailBlacklist, |
126 | MVideoWithAllFiles, | 126 | MVideoWithAllFiles, |
@@ -132,6 +132,7 @@ import { MThumbnail } from '../../typings/models/video/thumbnail' | |||
132 | import { VideoFile } from '@shared/models/videos/video-file.model' | 132 | import { VideoFile } from '@shared/models/videos/video-file.model' |
133 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | 133 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' |
134 | import validator from 'validator' | 134 | import validator from 'validator' |
135 | import { ModelCache } from '@server/models/model-cache' | ||
135 | 136 | ||
136 | export enum ScopeNames { | 137 | export enum ScopeNames { |
137 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', | 138 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', |
@@ -144,6 +145,7 @@ export enum ScopeNames { | |||
144 | WITH_USER_HISTORY = 'WITH_USER_HISTORY', | 145 | WITH_USER_HISTORY = 'WITH_USER_HISTORY', |
145 | WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', | 146 | WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', |
146 | WITH_USER_ID = 'WITH_USER_ID', | 147 | WITH_USER_ID = 'WITH_USER_ID', |
148 | WITH_IMMUTABLE_ATTRIBUTES = 'WITH_IMMUTABLE_ATTRIBUTES', | ||
147 | WITH_THUMBNAILS = 'WITH_THUMBNAILS' | 149 | WITH_THUMBNAILS = 'WITH_THUMBNAILS' |
148 | } | 150 | } |
149 | 151 | ||
@@ -187,6 +189,9 @@ export type AvailableForListIDsOptions = { | |||
187 | } | 189 | } |
188 | 190 | ||
189 | @Scopes(() => ({ | 191 | @Scopes(() => ({ |
192 | [ScopeNames.WITH_IMMUTABLE_ATTRIBUTES]: { | ||
193 | attributes: [ 'id', 'url', 'uuid', 'remote' ] | ||
194 | }, | ||
190 | [ScopeNames.FOR_API]: (options: ForAPIOptions) => { | 195 | [ScopeNames.FOR_API]: (options: ForAPIOptions) => { |
191 | const query: FindOptions = { | 196 | const query: FindOptions = { |
192 | include: [ | 197 | include: [ |
@@ -1074,6 +1079,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1074 | return undefined | 1079 | return undefined |
1075 | } | 1080 | } |
1076 | 1081 | ||
1082 | @BeforeDestroy | ||
1083 | static invalidateCache (instance: VideoModel) { | ||
1084 | ModelCache.Instance.invalidateCache('video', instance.id) | ||
1085 | } | ||
1086 | |||
1077 | static listLocal (): Bluebird<MVideoWithAllFiles[]> { | 1087 | static listLocal (): Bluebird<MVideoWithAllFiles[]> { |
1078 | const query = { | 1088 | const query = { |
1079 | where: { | 1089 | where: { |
@@ -1468,6 +1478,24 @@ export class VideoModel extends Model<VideoModel> { | |||
1468 | ]).findOne(options) | 1478 | ]).findOne(options) |
1469 | } | 1479 | } |
1470 | 1480 | ||
1481 | static loadImmutableAttributes (id: number | string, t?: Transaction): Bluebird<MVideoImmutable> { | ||
1482 | const fun = () => { | ||
1483 | const query = { | ||
1484 | where: buildWhereIdOrUUID(id), | ||
1485 | transaction: t | ||
1486 | } | ||
1487 | |||
1488 | return VideoModel.scope(ScopeNames.WITH_IMMUTABLE_ATTRIBUTES).findOne(query) | ||
1489 | } | ||
1490 | |||
1491 | return ModelCache.Instance.doCache({ | ||
1492 | cacheType: 'load-video-immutable-id', | ||
1493 | key: '' + id, | ||
1494 | deleteKey: 'video', | ||
1495 | fun | ||
1496 | }) | ||
1497 | } | ||
1498 | |||
1471 | static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> { | 1499 | static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> { |
1472 | const where = buildWhereIdOrUUID(id) | 1500 | const where = buildWhereIdOrUUID(id) |
1473 | const options = { | 1501 | const options = { |
@@ -1531,6 +1559,26 @@ export class VideoModel extends Model<VideoModel> { | |||
1531 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) | 1559 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) |
1532 | } | 1560 | } |
1533 | 1561 | ||
1562 | static loadByUrlImmutableAttributes (url: string, transaction?: Transaction): Bluebird<MVideoImmutable> { | ||
1563 | const fun = () => { | ||
1564 | const query: FindOptions = { | ||
1565 | where: { | ||
1566 | url | ||
1567 | }, | ||
1568 | transaction | ||
1569 | } | ||
1570 | |||
1571 | return VideoModel.scope(ScopeNames.WITH_IMMUTABLE_ATTRIBUTES).findOne(query) | ||
1572 | } | ||
1573 | |||
1574 | return ModelCache.Instance.doCache({ | ||
1575 | cacheType: 'load-video-immutable-url', | ||
1576 | key: url, | ||
1577 | deleteKey: 'video', | ||
1578 | fun | ||
1579 | }) | ||
1580 | } | ||
1581 | |||
1534 | static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> { | 1582 | static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> { |
1535 | const query: FindOptions = { | 1583 | const query: FindOptions = { |
1536 | where: { | 1584 | where: { |
diff --git a/server/typings/express.ts b/server/typings/express.ts index 43a9b2c99..f4188bf3d 100644 --- a/server/typings/express.ts +++ b/server/typings/express.ts | |||
@@ -21,7 +21,7 @@ import { | |||
21 | } from './models' | 21 | } from './models' |
22 | import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist' | 22 | import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist' |
23 | import { MVideoImportDefault } from '@server/typings/models/video/video-import' | 23 | import { MVideoImportDefault } from '@server/typings/models/video/video-import' |
24 | import { MAccountBlocklist, MActorUrl, MStreamingPlaylist, MVideoFile } from '@server/typings/models' | 24 | import { MAccountBlocklist, MActorUrl, MStreamingPlaylist, MVideoFile, MVideoImmutable } from '@server/typings/models' |
25 | import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element' | 25 | import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element' |
26 | import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate' | 26 | import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate' |
27 | import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership' | 27 | import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership' |
@@ -35,6 +35,7 @@ declare module 'express' { | |||
35 | 35 | ||
36 | locals: { | 36 | locals: { |
37 | videoAll?: MVideoFullLight | 37 | videoAll?: MVideoFullLight |
38 | onlyImmutableVideo?: MVideoImmutable | ||
38 | onlyVideo?: MVideoThumbnail | 39 | onlyVideo?: MVideoThumbnail |
39 | onlyVideoWithRights?: MVideoWithRights | 40 | onlyVideoWithRights?: MVideoWithRights |
40 | videoId?: MVideoIdThumbnail | 41 | videoId?: MVideoIdThumbnail |
diff --git a/server/typings/models/video/video.ts b/server/typings/models/video/video.ts index 7eff0a913..022a9566d 100644 --- a/server/typings/models/video/video.ts +++ b/server/typings/models/video/video.ts | |||
@@ -37,6 +37,7 @@ export type MVideoId = Pick<MVideo, 'id'> | |||
37 | export type MVideoUrl = Pick<MVideo, 'url'> | 37 | export type MVideoUrl = Pick<MVideo, 'url'> |
38 | export type MVideoUUID = Pick<MVideo, 'uuid'> | 38 | export type MVideoUUID = Pick<MVideo, 'uuid'> |
39 | 39 | ||
40 | export type MVideoImmutable = Pick<MVideo, 'id' | 'url' | 'uuid' | 'remote' | 'isOwned'> | ||
40 | export type MVideoIdUrl = MVideoId & MVideoUrl | 41 | export type MVideoIdUrl = MVideoId & MVideoUrl |
41 | export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'> | 42 | export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'> |
42 | 43 | ||
@@ -2282,10 +2282,10 @@ express-oauth-server@^2.0.0: | |||
2282 | express "^4.13.3" | 2282 | express "^4.13.3" |
2283 | oauth2-server "3.0.0" | 2283 | oauth2-server "3.0.0" |
2284 | 2284 | ||
2285 | express-rate-limit@^4.0.4: | 2285 | express-rate-limit@^5.0.0: |
2286 | version "4.0.4" | 2286 | version "5.0.0" |
2287 | resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-4.0.4.tgz#a495338ae9e58c856b66d1346ec0d86f43ba2e43" | 2287 | resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-5.0.0.tgz#9a6f4cacc388c1a1da7ba2f65db69f7395e9b04e" |
2288 | integrity sha512-DLRj2vMO7Xgai8qWKU9O6ZztF2bdDmfFNFi9k3G9BPzJ+7MG7eWaaBikbe0eBpNGSxU8JziwW0PQKG78aNWa6g== | 2288 | integrity sha512-dhT57wqxfqmkOi4HM7NuT4Gd7gbUgSK2ocG27Y6lwm8lbOAw9XQfeANawGq8wLDtlGPO1ZgDj0HmKsykTxfFAg== |
2289 | 2289 | ||
2290 | express-validator@^6.4.0: | 2290 | express-validator@^6.4.0: |
2291 | version "6.4.0" | 2291 | version "6.4.0" |