diff options
author | Chocobozzz <me@florianbigard.com> | 2022-03-24 13:36:47 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2022-04-15 09:49:35 +0200 |
commit | b211106695bb82f6c32e53306081b5262c3d109d (patch) | |
tree | fa187de1c33b0956665f5362e29af6b0f6d8bb57 /server/initializers | |
parent | 69d48ee30c9d47cddf0c3c047dc99a99dcb6e894 (diff) | |
download | PeerTube-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/initializers')
-rw-r--r-- | server/initializers/checker-before-init.ts | 1 | ||||
-rw-r--r-- | server/initializers/config.ts | 6 | ||||
-rw-r--r-- | server/initializers/constants.ts | 17 | ||||
-rw-r--r-- | server/initializers/database.ts | 10 | ||||
-rw-r--r-- | server/initializers/migrations/0705-local-video-viewers.ts | 52 |
5 files changed, 81 insertions, 5 deletions
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index 0f23a2d73..f2ef3d567 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts | |||
@@ -44,6 +44,7 @@ function checkMissedConfig () { | |||
44 | 'history.videos.max_age', 'views.videos.remote.max_age', 'views.videos.local_buffer_update_interval', 'views.videos.ip_view_expiration', | 44 | 'history.videos.max_age', 'views.videos.remote.max_age', 'views.videos.local_buffer_update_interval', 'views.videos.ip_view_expiration', |
45 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', | 45 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', |
46 | 'theme.default', | 46 | 'theme.default', |
47 | 'geo_ip.enabled', 'geo_ip.country.database_url', | ||
47 | 'remote_redundancy.videos.accept_from', | 48 | 'remote_redundancy.videos.accept_from', |
48 | 'federation.videos.federate_unlisted', 'federation.videos.cleanup_remote_interactions', | 49 | 'federation.videos.federate_unlisted', 'federation.videos.cleanup_remote_interactions', |
49 | 'peertube.check_latest_version.enabled', 'peertube.check_latest_version.url', | 50 | 'peertube.check_latest_version.enabled', 'peertube.check_latest_version.url', |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 122cb9472..d8f5f3496 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -215,6 +215,12 @@ const CONFIG = { | |||
215 | IP_VIEW_EXPIRATION: parseDurationToMs(config.get('views.videos.ip_view_expiration')) | 215 | IP_VIEW_EXPIRATION: parseDurationToMs(config.get('views.videos.ip_view_expiration')) |
216 | } | 216 | } |
217 | }, | 217 | }, |
218 | GEO_IP: { | ||
219 | ENABLED: config.get<boolean>('geo_ip.enabled'), | ||
220 | COUNTRY: { | ||
221 | DATABASE_URL: config.get<string>('geo_ip.country.database_url') | ||
222 | } | ||
223 | }, | ||
218 | PLUGINS: { | 224 | PLUGINS: { |
219 | INDEX: { | 225 | INDEX: { |
220 | ENABLED: config.get<boolean>('plugins.index.enabled'), | 226 | ENABLED: config.get<boolean>('plugins.index.enabled'), |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 6bcefe0db..4929923dc 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
24 | 24 | ||
25 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
26 | 26 | ||
27 | const LAST_MIGRATION_VERSION = 700 | 27 | const LAST_MIGRATION_VERSION = 705 |
28 | 28 | ||
29 | // --------------------------------------------------------------------------- | 29 | // --------------------------------------------------------------------------- |
30 | 30 | ||
@@ -228,6 +228,7 @@ const SCHEDULER_INTERVALS_MS = { | |||
228 | REMOVE_OLD_JOBS: 60000 * 60, // 1 hour | 228 | REMOVE_OLD_JOBS: 60000 * 60, // 1 hour |
229 | UPDATE_VIDEOS: 60000, // 1 minute | 229 | UPDATE_VIDEOS: 60000, // 1 minute |
230 | YOUTUBE_DL_UPDATE: 60000 * 60 * 24, // 1 day | 230 | YOUTUBE_DL_UPDATE: 60000 * 60 * 24, // 1 day |
231 | GEO_IP_UPDATE: 60000 * 60 * 24, // 1 day | ||
231 | VIDEO_VIEWS_BUFFER_UPDATE: CONFIG.VIEWS.VIDEOS.LOCAL_BUFFER_UPDATE_INTERVAL, | 232 | VIDEO_VIEWS_BUFFER_UPDATE: CONFIG.VIEWS.VIDEOS.LOCAL_BUFFER_UPDATE_INTERVAL, |
232 | CHECK_PLUGINS: CONFIG.PLUGINS.INDEX.CHECK_LATEST_VERSIONS_INTERVAL, | 233 | CHECK_PLUGINS: CONFIG.PLUGINS.INDEX.CHECK_LATEST_VERSIONS_INTERVAL, |
233 | CHECK_PEERTUBE_VERSION: 60000 * 60 * 24, // 1 day | 234 | CHECK_PEERTUBE_VERSION: 60000 * 60 * 24, // 1 day |
@@ -366,9 +367,12 @@ const CONSTRAINTS_FIELDS = { | |||
366 | 367 | ||
367 | const VIEW_LIFETIME = { | 368 | const VIEW_LIFETIME = { |
368 | VIEW: CONFIG.VIEWS.VIDEOS.IP_VIEW_EXPIRATION, | 369 | VIEW: CONFIG.VIEWS.VIDEOS.IP_VIEW_EXPIRATION, |
369 | VIEWER: 60000 * 5 // 5 minutes | 370 | VIEWER: 60000 * 5, // 5 minutes |
371 | VIEWER_STATS: 60000 * 60 // 1 hour | ||
370 | } | 372 | } |
371 | 373 | ||
374 | const MAX_LOCAL_VIEWER_WATCH_SECTIONS = 10 | ||
375 | |||
372 | let CONTACT_FORM_LIFETIME = 60000 * 60 // 1 hour | 376 | let CONTACT_FORM_LIFETIME = 60000 * 60 // 1 hour |
373 | 377 | ||
374 | const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = { | 378 | const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = { |
@@ -800,6 +804,12 @@ const SEARCH_INDEX = { | |||
800 | 804 | ||
801 | // --------------------------------------------------------------------------- | 805 | // --------------------------------------------------------------------------- |
802 | 806 | ||
807 | const STATS_TIMESERIE = { | ||
808 | MAX_DAYS: 30 | ||
809 | } | ||
810 | |||
811 | // --------------------------------------------------------------------------- | ||
812 | |||
803 | // Special constants for a test instance | 813 | // Special constants for a test instance |
804 | if (isTestInstance() === true) { | 814 | if (isTestInstance() === true) { |
805 | PRIVATE_RSA_KEY_SIZE = 1024 | 815 | PRIVATE_RSA_KEY_SIZE = 1024 |
@@ -836,6 +846,7 @@ if (isTestInstance() === true) { | |||
836 | REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 | 846 | REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 |
837 | 847 | ||
838 | VIEW_LIFETIME.VIEWER = 1000 * 5 // 5 second | 848 | VIEW_LIFETIME.VIEWER = 1000 * 5 // 5 second |
849 | VIEW_LIFETIME.VIEWER_STATS = 1000 * 5 // 5 second | ||
839 | CONTACT_FORM_LIFETIME = 1000 // 1 second | 850 | CONTACT_FORM_LIFETIME = 1000 // 1 second |
840 | 851 | ||
841 | JOB_ATTEMPTS['email'] = 1 | 852 | JOB_ATTEMPTS['email'] = 1 |
@@ -907,6 +918,7 @@ export { | |||
907 | LAST_MIGRATION_VERSION, | 918 | LAST_MIGRATION_VERSION, |
908 | OAUTH_LIFETIME, | 919 | OAUTH_LIFETIME, |
909 | CUSTOM_HTML_TAG_COMMENTS, | 920 | CUSTOM_HTML_TAG_COMMENTS, |
921 | STATS_TIMESERIE, | ||
910 | BROADCAST_CONCURRENCY, | 922 | BROADCAST_CONCURRENCY, |
911 | AUDIT_LOG_FILENAME, | 923 | AUDIT_LOG_FILENAME, |
912 | PAGINATION, | 924 | PAGINATION, |
@@ -949,6 +961,7 @@ export { | |||
949 | ABUSE_STATES, | 961 | ABUSE_STATES, |
950 | LRU_CACHE, | 962 | LRU_CACHE, |
951 | REQUEST_TIMEOUTS, | 963 | REQUEST_TIMEOUTS, |
964 | MAX_LOCAL_VIEWER_WATCH_SECTIONS, | ||
952 | USER_PASSWORD_RESET_LIFETIME, | 965 | USER_PASSWORD_RESET_LIFETIME, |
953 | USER_PASSWORD_CREATE_LIFETIME, | 966 | USER_PASSWORD_CREATE_LIFETIME, |
954 | MEMOIZE_TTL, | 967 | MEMOIZE_TTL, |
diff --git a/server/initializers/database.ts b/server/initializers/database.ts index 0e690f6ae..7a7ba61f4 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts | |||
@@ -1,10 +1,14 @@ | |||
1 | import { QueryTypes, Transaction } from 'sequelize' | 1 | import { QueryTypes, Transaction } from 'sequelize' |
2 | import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' | 2 | import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' |
3 | import { ActorCustomPageModel } from '@server/models/account/actor-custom-page' | ||
3 | import { TrackerModel } from '@server/models/server/tracker' | 4 | import { TrackerModel } from '@server/models/server/tracker' |
4 | import { VideoTrackerModel } from '@server/models/server/video-tracker' | 5 | import { VideoTrackerModel } from '@server/models/server/video-tracker' |
5 | import { UserModel } from '@server/models/user/user' | 6 | import { UserModel } from '@server/models/user/user' |
6 | import { UserNotificationModel } from '@server/models/user/user-notification' | 7 | import { UserNotificationModel } from '@server/models/user/user-notification' |
7 | import { UserVideoHistoryModel } from '@server/models/user/user-video-history' | 8 | import { UserVideoHistoryModel } from '@server/models/user/user-video-history' |
9 | import { VideoJobInfoModel } from '@server/models/video/video-job-info' | ||
10 | import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer' | ||
11 | import { LocalVideoViewerWatchSectionModel } from '@server/models/view/local-video-viewer-watch-section' | ||
8 | import { isTestInstance } from '../helpers/core-utils' | 12 | import { isTestInstance } from '../helpers/core-utils' |
9 | import { logger } from '../helpers/logger' | 13 | import { logger } from '../helpers/logger' |
10 | import { AbuseModel } from '../models/abuse/abuse' | 14 | import { AbuseModel } from '../models/abuse/abuse' |
@@ -42,10 +46,8 @@ import { VideoPlaylistElementModel } from '../models/video/video-playlist-elemen | |||
42 | import { VideoShareModel } from '../models/video/video-share' | 46 | import { VideoShareModel } from '../models/video/video-share' |
43 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 47 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
44 | import { VideoTagModel } from '../models/video/video-tag' | 48 | import { VideoTagModel } from '../models/video/video-tag' |
45 | import { VideoViewModel } from '../models/video/video-view' | 49 | import { VideoViewModel } from '../models/view/video-view' |
46 | import { CONFIG } from './config' | 50 | import { CONFIG } from './config' |
47 | import { ActorCustomPageModel } from '@server/models/account/actor-custom-page' | ||
48 | import { VideoJobInfoModel } from '@server/models/video/video-job-info' | ||
49 | 51 | ||
50 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string | 52 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string |
51 | 53 | ||
@@ -140,6 +142,8 @@ async function initDatabaseModels (silent: boolean) { | |||
140 | VideoStreamingPlaylistModel, | 142 | VideoStreamingPlaylistModel, |
141 | VideoPlaylistModel, | 143 | VideoPlaylistModel, |
142 | VideoPlaylistElementModel, | 144 | VideoPlaylistElementModel, |
145 | LocalVideoViewerModel, | ||
146 | LocalVideoViewerWatchSectionModel, | ||
143 | ThumbnailModel, | 147 | ThumbnailModel, |
144 | TrackerModel, | 148 | TrackerModel, |
145 | VideoTrackerModel, | 149 | VideoTrackerModel, |
diff --git a/server/initializers/migrations/0705-local-video-viewers.ts b/server/initializers/migrations/0705-local-video-viewers.ts new file mode 100644 index 000000000..123402641 --- /dev/null +++ b/server/initializers/migrations/0705-local-video-viewers.ts | |||
@@ -0,0 +1,52 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction | ||
5 | queryInterface: Sequelize.QueryInterface | ||
6 | sequelize: Sequelize.Sequelize | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | const { transaction } = utils | ||
10 | |||
11 | { | ||
12 | const query = ` | ||
13 | CREATE TABLE IF NOT EXISTS "localVideoViewer" ( | ||
14 | "id" serial, | ||
15 | "startDate" timestamp with time zone NOT NULL, | ||
16 | "endDate" timestamp with time zone NOT NULL, | ||
17 | "watchTime" integer NOT NULL, | ||
18 | "country" varchar(255), | ||
19 | "uuid" uuid NOT NULL, | ||
20 | "url" varchar(255) NOT NULL, | ||
21 | "videoId" integer NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE, | ||
22 | "createdAt" timestamp with time zone NOT NULL, | ||
23 | PRIMARY KEY ("id") | ||
24 | ); | ||
25 | ` | ||
26 | await utils.sequelize.query(query, { transaction }) | ||
27 | } | ||
28 | |||
29 | { | ||
30 | const query = ` | ||
31 | CREATE TABLE IF NOT EXISTS "localVideoViewerWatchSection" ( | ||
32 | "id" serial, | ||
33 | "watchStart" integer NOT NULL, | ||
34 | "watchEnd" integer NOT NULL, | ||
35 | "localVideoViewerId" integer NOT NULL REFERENCES "localVideoViewer" ("id") ON DELETE CASCADE ON UPDATE CASCADE, | ||
36 | "createdAt" timestamp with time zone NOT NULL, | ||
37 | PRIMARY KEY ("id") | ||
38 | ); | ||
39 | ` | ||
40 | await utils.sequelize.query(query, { transaction }) | ||
41 | } | ||
42 | |||
43 | } | ||
44 | |||
45 | function down () { | ||
46 | throw new Error('Not implemented.') | ||
47 | } | ||
48 | |||
49 | export { | ||
50 | up, | ||
51 | down | ||
52 | } | ||