aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/middlewares
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-03-24 13:36:47 +0100
committerChocobozzz <chocobozzz@cpy.re>2022-04-15 09:49:35 +0200
commitb211106695bb82f6c32e53306081b5262c3d109d (patch)
treefa187de1c33b0956665f5362e29af6b0f6d8bb57 /server/middlewares
parent69d48ee30c9d47cddf0c3c047dc99a99dcb6e894 (diff)
downloadPeerTube-b211106695bb82f6c32e53306081b5262c3d109d.tar.gz
PeerTube-b211106695bb82f6c32e53306081b5262c3d109d.tar.zst
PeerTube-b211106695bb82f6c32e53306081b5262c3d109d.zip
Support video views/viewers stats in server
* Add "currentTime" and "event" body params to view endpoint * Merge watching and view endpoints * Introduce WatchAction AP activity * Add tables to store viewer information of local videos * Add endpoints to fetch video views/viewers stats of local videos * Refactor views/viewers handlers * Support "views" and "viewers" counters for both VOD and live videos
Diffstat (limited to 'server/middlewares')
-rw-r--r--server/middlewares/cache/shared/api-cache.ts4
-rw-r--r--server/middlewares/validators/express.ts15
-rw-r--r--server/middlewares/validators/index.ts23
-rw-r--r--server/middlewares/validators/videos/index.ts3
-rw-r--r--server/middlewares/validators/videos/video-stats.ts73
-rw-r--r--server/middlewares/validators/videos/video-view.ts74
-rw-r--r--server/middlewares/validators/videos/video-watch.ts38
7 files changed, 182 insertions, 48 deletions
diff --git a/server/middlewares/cache/shared/api-cache.ts b/server/middlewares/cache/shared/api-cache.ts
index 86c5095b5..abc919339 100644
--- a/server/middlewares/cache/shared/api-cache.ts
+++ b/server/middlewares/cache/shared/api-cache.ts
@@ -6,8 +6,8 @@ import { OutgoingHttpHeaders } from 'http'
6import { isTestInstance, parseDurationToMs } from '@server/helpers/core-utils' 6import { isTestInstance, parseDurationToMs } from '@server/helpers/core-utils'
7import { logger } from '@server/helpers/logger' 7import { logger } from '@server/helpers/logger'
8import { Redis } from '@server/lib/redis' 8import { Redis } from '@server/lib/redis'
9import { HttpStatusCode } from '@shared/models'
10import { asyncMiddleware } from '@server/middlewares' 9import { asyncMiddleware } from '@server/middlewares'
10import { HttpStatusCode } from '@shared/models'
11 11
12export interface APICacheOptions { 12export interface APICacheOptions {
13 headerBlacklist?: string[] 13 headerBlacklist?: string[]
@@ -152,7 +152,7 @@ export class ApiCache {
152 end: res.end, 152 end: res.end,
153 cacheable: true, 153 cacheable: true,
154 content: undefined, 154 content: undefined,
155 headers: {} 155 headers: undefined
156 } 156 }
157 157
158 // Patch express 158 // Patch express
diff --git a/server/middlewares/validators/express.ts b/server/middlewares/validators/express.ts
new file mode 100644
index 000000000..718aec55b
--- /dev/null
+++ b/server/middlewares/validators/express.ts
@@ -0,0 +1,15 @@
1import * as express from 'express'
2
3const methodsValidator = (methods: string[]) => {
4 return (req: express.Request, res: express.Response, next: express.NextFunction) => {
5 if (methods.includes(req.method) !== true) {
6 return res.sendStatus(405)
7 }
8
9 return next()
10 }
11}
12
13export {
14 methodsValidator
15}
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts
index 94a3c2dea..b0ad04819 100644
--- a/server/middlewares/validators/index.ts
+++ b/server/middlewares/validators/index.ts
@@ -1,17 +1,26 @@
1export * from './activitypub'
2export * from './videos'
1export * from './abuse' 3export * from './abuse'
2export * from './account' 4export * from './account'
3export * from './actor-image' 5export * from './actor-image'
4export * from './blocklist' 6export * from './blocklist'
7export * from './bulk'
8export * from './config'
9export * from './express'
10export * from './feeds'
11export * from './follows'
12export * from './jobs'
13export * from './logs'
5export * from './oembed' 14export * from './oembed'
6export * from './activitypub'
7export * from './pagination' 15export * from './pagination'
8export * from './follows' 16export * from './plugins'
9export * from './feeds' 17export * from './redundancy'
10export * from './sort'
11export * from './users'
12export * from './user-subscriptions'
13export * from './videos'
14export * from './search' 18export * from './search'
15export * from './server' 19export * from './server'
20export * from './sort'
21export * from './themes'
16export * from './user-history' 22export * from './user-history'
23export * from './user-notifications'
24export * from './user-subscriptions'
25export * from './users'
17export * from './webfinger' 26export * from './webfinger'
diff --git a/server/middlewares/validators/videos/index.ts b/server/middlewares/validators/videos/index.ts
index c7dea4b3d..bd2590bc5 100644
--- a/server/middlewares/validators/videos/index.ts
+++ b/server/middlewares/validators/videos/index.ts
@@ -6,9 +6,10 @@ export * from './video-files'
6export * from './video-imports' 6export * from './video-imports'
7export * from './video-live' 7export * from './video-live'
8export * from './video-ownership-changes' 8export * from './video-ownership-changes'
9export * from './video-watch' 9export * from './video-view'
10export * from './video-rates' 10export * from './video-rates'
11export * from './video-shares' 11export * from './video-shares'
12export * from './video-stats'
12export * from './video-studio' 13export * from './video-studio'
13export * from './video-transcoding' 14export * from './video-transcoding'
14export * from './videos' 15export * from './videos'
diff --git a/server/middlewares/validators/videos/video-stats.ts b/server/middlewares/validators/videos/video-stats.ts
new file mode 100644
index 000000000..358b6b473
--- /dev/null
+++ b/server/middlewares/validators/videos/video-stats.ts
@@ -0,0 +1,73 @@
1import express from 'express'
2import { param } from 'express-validator'
3import { isValidStatTimeserieMetric } from '@server/helpers/custom-validators/video-stats'
4import { HttpStatusCode, UserRight } from '@shared/models'
5import { logger } from '../../../helpers/logger'
6import { areValidationErrors, checkUserCanManageVideo, doesVideoExist, isValidVideoIdParam } from '../shared'
7
8const videoOverallStatsValidator = [
9 isValidVideoIdParam('videoId'),
10
11 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
12 logger.debug('Checking videoOverallStatsValidator parameters', { parameters: req.body })
13
14 if (areValidationErrors(req, res)) return
15 if (!await commonStatsCheck(req, res)) return
16
17 return next()
18 }
19]
20
21const videoRetentionStatsValidator = [
22 isValidVideoIdParam('videoId'),
23
24 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
25 logger.debug('Checking videoRetentionStatsValidator parameters', { parameters: req.body })
26
27 if (areValidationErrors(req, res)) return
28 if (!await commonStatsCheck(req, res)) return
29
30 if (res.locals.videoAll.isLive) {
31 return res.fail({
32 status: HttpStatusCode.BAD_REQUEST_400,
33 message: 'Cannot get retention stats of live video'
34 })
35 }
36
37 return next()
38 }
39]
40
41const videoTimeserieStatsValidator = [
42 isValidVideoIdParam('videoId'),
43
44 param('metric')
45 .custom(isValidStatTimeserieMetric)
46 .withMessage('Should have a valid timeserie metric'),
47
48 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
49 logger.debug('Checking videoTimeserieStatsValidator parameters', { parameters: req.body })
50
51 if (areValidationErrors(req, res)) return
52 if (!await commonStatsCheck(req, res)) return
53
54 return next()
55 }
56]
57
58// ---------------------------------------------------------------------------
59
60export {
61 videoOverallStatsValidator,
62 videoTimeserieStatsValidator,
63 videoRetentionStatsValidator
64}
65
66// ---------------------------------------------------------------------------
67
68async function commonStatsCheck (req: express.Request, res: express.Response) {
69 if (!await doesVideoExist(req.params.videoId, res, 'all')) return false
70 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.SEE_ALL_VIDEOS, res)) return false
71
72 return true
73}
diff --git a/server/middlewares/validators/videos/video-view.ts b/server/middlewares/validators/videos/video-view.ts
new file mode 100644
index 000000000..7a4994e8a
--- /dev/null
+++ b/server/middlewares/validators/videos/video-view.ts
@@ -0,0 +1,74 @@
1import express from 'express'
2import { body, param } from 'express-validator'
3import { isVideoTimeValid } from '@server/helpers/custom-validators/video-view'
4import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer'
5import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
6import { exists, isIdValid, isIntOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc'
7import { logger } from '../../../helpers/logger'
8import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared'
9
10const getVideoLocalViewerValidator = [
11 param('localViewerId')
12 .custom(isIdValid).withMessage('Should have a valid local viewer id'),
13
14 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
15 logger.debug('Checking getVideoLocalViewerValidator parameters', { parameters: req.params })
16
17 if (areValidationErrors(req, res)) return
18
19 const localViewer = await LocalVideoViewerModel.loadFullById(+req.params.localViewerId)
20 if (!localViewer) {
21 return res.fail({
22 status: HttpStatusCode.NOT_FOUND_404,
23 message: 'Local viewer not found'
24 })
25 }
26
27 res.locals.localViewerFull = localViewer
28
29 return next()
30 }
31]
32
33const videoViewValidator = [
34 isValidVideoIdParam('videoId'),
35
36 body('currentTime')
37 .optional() // TODO: remove optional in a few versions, introduced in 4.2
38 .customSanitizer(toIntOrNull)
39 .custom(isIntOrNull).withMessage('Should have correct current time'),
40
41 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
42 logger.debug('Checking videoView parameters', { parameters: req.body })
43
44 if (areValidationErrors(req, res)) return
45 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
46
47 const video = res.locals.onlyVideo
48 const videoDuration = video.isLive
49 ? undefined
50 : video.duration
51
52 if (!exists(req.body.currentTime)) { // TODO: remove in a few versions, introduced in 4.2
53 req.body.currentTime = Math.min(videoDuration ?? 0, 30)
54 }
55
56 const currentTime: number = req.body.currentTime
57
58 if (!isVideoTimeValid(currentTime, videoDuration)) {
59 return res.fail({
60 status: HttpStatusCode.BAD_REQUEST_400,
61 message: 'Current time is invalid'
62 })
63 }
64
65 return next()
66 }
67]
68
69// ---------------------------------------------------------------------------
70
71export {
72 videoViewValidator,
73 getVideoLocalViewerValidator
74}
diff --git a/server/middlewares/validators/videos/video-watch.ts b/server/middlewares/validators/videos/video-watch.ts
deleted file mode 100644
index d83710a64..000000000
--- a/server/middlewares/validators/videos/video-watch.ts
+++ /dev/null
@@ -1,38 +0,0 @@
1import express from 'express'
2import { body } from 'express-validator'
3import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
4import { toIntOrNull } from '../../../helpers/custom-validators/misc'
5import { logger } from '../../../helpers/logger'
6import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared'
7
8const videoWatchingValidator = [
9 isValidVideoIdParam('videoId'),
10
11 body('currentTime')
12 .customSanitizer(toIntOrNull)
13 .isInt().withMessage('Should have correct current time'),
14
15 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
16 logger.debug('Checking videoWatching parameters', { parameters: req.body })
17
18 if (areValidationErrors(req, res)) return
19 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
20
21 const user = res.locals.oauth.token.User
22 if (user.videosHistoryEnabled === false) {
23 logger.warn('Cannot set videos to watch by user %d: videos history is disabled.', user.id)
24 return res.fail({
25 status: HttpStatusCode.CONFLICT_409,
26 message: 'Video history is disabled'
27 })
28 }
29
30 return next()
31 }
32]
33
34// ---------------------------------------------------------------------------
35
36export {
37 videoWatchingValidator
38}