aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/initializers
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-02-11 11:52:34 +0100
committerChocobozzz <me@florianbigard.com>2019-02-11 11:52:34 +0100
commit88108880bbdba473cfe36ecbebc1c3c4f972e102 (patch)
treeb242efb3b4f0d7e49d88f2d1f2063b5b3b0489c0 /server/initializers
parent53a94c7cfa8368da4cd248d65df8346905938f0c (diff)
parent9b712a2017e4ab3cf12cd6bd58278905520159d0 (diff)
downloadPeerTube-88108880bbdba473cfe36ecbebc1c3c4f972e102.tar.gz
PeerTube-88108880bbdba473cfe36ecbebc1c3c4f972e102.tar.zst
PeerTube-88108880bbdba473cfe36ecbebc1c3c4f972e102.zip
Merge branch 'develop' into pr/1217
Diffstat (limited to 'server/initializers')
-rw-r--r--server/initializers/checker-after-init.ts14
-rw-r--r--server/initializers/checker-before-init.ts13
-rw-r--r--server/initializers/constants.ts196
-rw-r--r--server/initializers/database.ts44
-rw-r--r--server/initializers/installer.ts26
-rw-r--r--server/initializers/migrations/0120-video-null.ts3
-rw-r--r--server/initializers/migrations/0195-support.ts9
-rw-r--r--server/initializers/migrations/0245-user-blocked.ts3
-rw-r--r--server/initializers/migrations/0250-video-abuse-state.ts3
-rw-r--r--server/initializers/migrations/0255-video-blacklist-reason.ts3
-rw-r--r--server/initializers/migrations/0260-upload-quota-daily.ts1
-rw-r--r--server/initializers/migrations/0275-video-file-unique.ts6
-rw-r--r--server/initializers/migrations/0280-webtorrent-policy-user.ts28
-rw-r--r--server/initializers/migrations/0285-description-support.ts53
-rw-r--r--server/initializers/migrations/0290-account-video-rate-url.ts46
-rw-r--r--server/initializers/migrations/0295-video-file-extname.ts49
-rw-r--r--server/initializers/migrations/0300-user-videos-history-enabled.ts27
-rw-r--r--server/initializers/migrations/0305-fix-unfederated-videos.ts52
-rw-r--r--server/initializers/migrations/0310-drop-unused-video-indexes.ts32
-rw-r--r--server/initializers/migrations/0315-user-notifications.ts47
-rw-r--r--server/initializers/migrations/0320-blacklist-unfederate.ts27
-rw-r--r--server/initializers/migrations/0325-video-abuse-fields.ts37
-rw-r--r--server/initializers/migrations/0330-video-streaming-playlist.ts51
23 files changed, 671 insertions, 99 deletions
diff --git a/server/initializers/checker-after-init.ts b/server/initializers/checker-after-init.ts
index 72d846957..955d55206 100644
--- a/server/initializers/checker-after-init.ts
+++ b/server/initializers/checker-after-init.ts
@@ -10,6 +10,7 @@ import { getServerActor } from '../helpers/utils'
10import { RecentlyAddedStrategy } from '../../shared/models/redundancy' 10import { RecentlyAddedStrategy } from '../../shared/models/redundancy'
11import { isArray } from '../helpers/custom-validators/misc' 11import { isArray } from '../helpers/custom-validators/misc'
12import { uniq } from 'lodash' 12import { uniq } from 'lodash'
13import { Emailer } from '../lib/emailer'
13 14
14async function checkActivityPubUrls () { 15async function checkActivityPubUrls () {
15 const actor = await getServerActor() 16 const actor = await getServerActor()
@@ -32,9 +33,19 @@ async function checkActivityPubUrls () {
32// Some checks on configuration files 33// Some checks on configuration files
33// Return an error message, or null if everything is okay 34// Return an error message, or null if everything is okay
34function checkConfig () { 35function checkConfig () {
35 const defaultNSFWPolicy = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY 36
37 if (!Emailer.isEnabled()) {
38 if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
39 return 'Emailer is disabled but you require signup email verification.'
40 }
41
42 if (CONFIG.CONTACT_FORM.ENABLED) {
43 logger.warn('Emailer is disabled so the contact form will not work.')
44 }
45 }
36 46
37 // NSFW policy 47 // NSFW policy
48 const defaultNSFWPolicy = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY
38 { 49 {
39 const available = [ 'do_not_list', 'blur', 'display' ] 50 const available = [ 'do_not_list', 'blur', 'display' ]
40 if (available.indexOf(defaultNSFWPolicy) === -1) { 51 if (available.indexOf(defaultNSFWPolicy) === -1) {
@@ -68,6 +79,7 @@ function checkConfig () {
68 } 79 }
69 } 80 }
70 81
82 // Check storage directory locations
71 if (isProdInstance()) { 83 if (isProdInstance()) {
72 const configStorage = config.get('storage') 84 const configStorage = config.get('storage')
73 for (const key of Object.keys(configStorage)) { 85 for (const key of Object.keys(configStorage)) {
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts
index 4f46d406a..29fdb263e 100644
--- a/server/initializers/checker-before-init.ts
+++ b/server/initializers/checker-before-init.ts
@@ -12,13 +12,14 @@ function checkMissedConfig () {
12 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', 'database.pool.max', 12 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', 'database.pool.max',
13 'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address', 13 'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',
14 'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache', 14 'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
15 'storage.redundancy', 'storage.tmp', 'storage.playlists',
15 'log.level', 16 'log.level',
16 'user.video_quota', 'user.video_quota_daily', 17 'user.video_quota', 'user.video_quota_daily',
17 'cache.previews.size', 'admin.email', 18 'cache.previews.size', 'admin.email', 'contact_form.enabled',
18 'signup.enabled', 'signup.limit', 'signup.requires_email_verification', 19 'signup.enabled', 'signup.limit', 'signup.requires_email_verification',
19 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', 20 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
20 'redundancy.videos.strategies', 'redundancy.videos.check_interval', 21 'redundancy.videos.strategies', 'redundancy.videos.check_interval',
21 'transcoding.enabled', 'transcoding.threads', 22 'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions',
22 'import.videos.http.enabled', 'import.videos.torrent.enabled', 23 'import.videos.http.enabled', 'import.videos.torrent.enabled',
23 'trending.videos.interval_days', 24 'trending.videos.interval_days',
24 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', 25 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
@@ -77,7 +78,7 @@ async function checkFFmpeg (CONFIG: { TRANSCODING: { ENABLED: boolean } }) {
77 } 78 }
78 } 79 }
79 80
80 checkFFmpegEncoders() 81 return checkFFmpegEncoders()
81} 82}
82 83
83// Optional encoders, if present, can be used to improve transcoding 84// Optional encoders, if present, can be used to improve transcoding
@@ -95,10 +96,10 @@ async function checkFFmpegEncoders (): Promise<Map<string, boolean>> {
95 supportedOptionalEncoders = new Map<string, boolean>() 96 supportedOptionalEncoders = new Map<string, boolean>()
96 97
97 for (const encoder of optionalEncoders) { 98 for (const encoder of optionalEncoders) {
98 supportedOptionalEncoders.set(encoder, 99 supportedOptionalEncoders.set(encoder, encoders[encoder] !== undefined)
99 encoders[encoder] !== undefined
100 )
101 } 100 }
101
102 return supportedOptionalEncoders
102} 103}
103 104
104// --------------------------------------------------------------------------- 105// ---------------------------------------------------------------------------
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 1a3b52015..e5c4c4e63 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -3,9 +3,9 @@ import { dirname, join } from 'path'
3import { JobType, VideoRateType, VideoState, VideosRedundancy } from '../../shared/models' 3import { JobType, VideoRateType, VideoState, VideosRedundancy } from '../../shared/models'
4import { ActivityPubActorType } from '../../shared/models/activitypub' 4import { ActivityPubActorType } from '../../shared/models/activitypub'
5import { FollowState } from '../../shared/models/actors' 5import { FollowState } from '../../shared/models/actors'
6import { VideoAbuseState, VideoImportState, VideoPrivacy } from '../../shared/models/videos' 6import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos'
7// Do not use barrels, remain constants as independent as possible 7// Do not use barrels, remain constants as independent as possible
8import { buildPath, isTestInstance, parseDuration, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' 8import { buildPath, isTestInstance, parseDuration, parseBytes, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils'
9import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' 9import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
10import { invert } from 'lodash' 10import { invert } from 'lodash'
11import { CronRepeatOptions, EveryRepeatOptions } from 'bull' 11import { CronRepeatOptions, EveryRepeatOptions } from 'bull'
@@ -16,7 +16,7 @@ let config: IConfig = require('config')
16 16
17// --------------------------------------------------------------------------- 17// ---------------------------------------------------------------------------
18 18
19const LAST_MIGRATION_VERSION = 275 19const LAST_MIGRATION_VERSION = 330
20 20
21// --------------------------------------------------------------------------- 21// ---------------------------------------------------------------------------
22 22
@@ -47,7 +47,12 @@ const SORTABLE_COLUMNS = {
47 VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes', 'trending' ], 47 VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes', 'trending' ],
48 48
49 VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes', 'match' ], 49 VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes', 'match' ],
50 VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName', 'createdAt' ] 50 VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName', 'createdAt' ],
51
52 ACCOUNTS_BLOCKLIST: [ 'createdAt' ],
53 SERVERS_BLOCKLIST: [ 'createdAt' ],
54
55 USER_NOTIFICATIONS: [ 'createdAt' ]
51} 56}
52 57
53const OAUTH_LIFETIME = { 58const OAUTH_LIFETIME = {
@@ -58,6 +63,7 @@ const OAUTH_LIFETIME = {
58const ROUTE_CACHE_LIFETIME = { 63const ROUTE_CACHE_LIFETIME = {
59 FEEDS: '15 minutes', 64 FEEDS: '15 minutes',
60 ROBOTS: '2 hours', 65 ROBOTS: '2 hours',
66 SITEMAP: '1 day',
61 SECURITYTXT: '2 hours', 67 SECURITYTXT: '2 hours',
62 NODEINFO: '10 minutes', 68 NODEINFO: '10 minutes',
63 DNT_POLICY: '1 week', 69 DNT_POLICY: '1 week',
@@ -99,7 +105,8 @@ const JOB_ATTEMPTS: { [ id in JobType ]: number } = {
99 'video-file': 1, 105 'video-file': 1,
100 'video-import': 1, 106 'video-import': 1,
101 'email': 5, 107 'email': 5,
102 'videos-views': 1 108 'videos-views': 1,
109 'activitypub-refresher': 1
103} 110}
104const JOB_CONCURRENCY: { [ id in JobType ]: number } = { 111const JOB_CONCURRENCY: { [ id in JobType ]: number } = {
105 'activitypub-http-broadcast': 1, 112 'activitypub-http-broadcast': 1,
@@ -110,7 +117,8 @@ const JOB_CONCURRENCY: { [ id in JobType ]: number } = {
110 'video-file': 1, 117 'video-file': 1,
111 'video-import': 1, 118 'video-import': 1,
112 'email': 5, 119 'email': 5,
113 'videos-views': 1 120 'videos-views': 1,
121 'activitypub-refresher': 1
114} 122}
115const JOB_TTL: { [ id in JobType ]: number } = { 123const JOB_TTL: { [ id in JobType ]: number } = {
116 'activitypub-http-broadcast': 60000 * 10, // 10 minutes 124 'activitypub-http-broadcast': 60000 * 10, // 10 minutes
@@ -121,11 +129,12 @@ const JOB_TTL: { [ id in JobType ]: number } = {
121 'video-file': 1000 * 3600 * 48, // 2 days, transcoding could be long 129 'video-file': 1000 * 3600 * 48, // 2 days, transcoding could be long
122 'video-import': 1000 * 3600 * 2, // hours 130 'video-import': 1000 * 3600 * 2, // hours
123 'email': 60000 * 10, // 10 minutes 131 'email': 60000 * 10, // 10 minutes
124 'videos-views': undefined // Unlimited 132 'videos-views': undefined, // Unlimited
133 'activitypub-refresher': 60000 * 10 // 10 minutes
125} 134}
126const REPEAT_JOBS: { [ id: string ]: EveryRepeatOptions | CronRepeatOptions } = { 135const REPEAT_JOBS: { [ id: string ]: EveryRepeatOptions | CronRepeatOptions } = {
127 'videos-views': { 136 'videos-views': {
128 cron: '1 * * * *' // At 1 minutes past the hour 137 cron: '1 * * * *' // At 1 minute past the hour
129 } 138 }
130} 139}
131 140
@@ -137,7 +146,7 @@ const VIDEO_IMPORT_TIMEOUT = 1000 * 3600 // 1 hour
137 146
138// 1 hour 147// 1 hour
139let SCHEDULER_INTERVALS_MS = { 148let SCHEDULER_INTERVALS_MS = {
140 badActorFollow: 60000 * 60, // 1 hour 149 actorFollowScores: 60000 * 60, // 1 hour
141 removeOldJobs: 60000 * 60, // 1 hour 150 removeOldJobs: 60000 * 60, // 1 hour
142 updateVideos: 60000, // 1 minute 151 updateVideos: 60000, // 1 minute
143 youtubeDLUpdate: 60000 * 60 * 24 // 1 day 152 youtubeDLUpdate: 60000 * 60 * 24 // 1 day
@@ -179,9 +188,12 @@ const CONFIG = {
179 FROM_ADDRESS: config.get<string>('smtp.from_address') 188 FROM_ADDRESS: config.get<string>('smtp.from_address')
180 }, 189 },
181 STORAGE: { 190 STORAGE: {
191 TMP_DIR: buildPath(config.get<string>('storage.tmp')),
182 AVATARS_DIR: buildPath(config.get<string>('storage.avatars')), 192 AVATARS_DIR: buildPath(config.get<string>('storage.avatars')),
183 LOG_DIR: buildPath(config.get<string>('storage.logs')), 193 LOG_DIR: buildPath(config.get<string>('storage.logs')),
184 VIDEOS_DIR: buildPath(config.get<string>('storage.videos')), 194 VIDEOS_DIR: buildPath(config.get<string>('storage.videos')),
195 PLAYLISTS_DIR: buildPath(config.get<string>('storage.playlists')),
196 REDUNDANCY_DIR: buildPath(config.get<string>('storage.redundancy')),
185 THUMBNAILS_DIR: buildPath(config.get<string>('storage.thumbnails')), 197 THUMBNAILS_DIR: buildPath(config.get<string>('storage.thumbnails')),
186 PREVIEWS_DIR: buildPath(config.get<string>('storage.previews')), 198 PREVIEWS_DIR: buildPath(config.get<string>('storage.previews')),
187 CAPTIONS_DIR: buildPath(config.get<string>('storage.captions')), 199 CAPTIONS_DIR: buildPath(config.get<string>('storage.captions')),
@@ -220,6 +232,9 @@ const CONFIG = {
220 ADMIN: { 232 ADMIN: {
221 get EMAIL () { return config.get<string>('admin.email') } 233 get EMAIL () { return config.get<string>('admin.email') }
222 }, 234 },
235 CONTACT_FORM: {
236 get ENABLED () { return config.get<boolean>('contact_form.enabled') }
237 },
223 SIGNUP: { 238 SIGNUP: {
224 get ENABLED () { return config.get<boolean>('signup.enabled') }, 239 get ENABLED () { return config.get<boolean>('signup.enabled') },
225 get LIMIT () { return config.get<number>('signup.limit') }, 240 get LIMIT () { return config.get<number>('signup.limit') },
@@ -232,11 +247,12 @@ const CONFIG = {
232 } 247 }
233 }, 248 },
234 USER: { 249 USER: {
235 get VIDEO_QUOTA () { return config.get<number>('user.video_quota') }, 250 get VIDEO_QUOTA () { return parseBytes(config.get<number>('user.video_quota')) },
236 get VIDEO_QUOTA_DAILY () { return config.get<number>('user.video_quota_daily') } 251 get VIDEO_QUOTA_DAILY () { return parseBytes(config.get<number>('user.video_quota_daily')) }
237 }, 252 },
238 TRANSCODING: { 253 TRANSCODING: {
239 get ENABLED () { return config.get<boolean>('transcoding.enabled') }, 254 get ENABLED () { return config.get<boolean>('transcoding.enabled') },
255 get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
240 get THREADS () { return config.get<number>('transcoding.threads') }, 256 get THREADS () { return config.get<number>('transcoding.threads') },
241 RESOLUTIONS: { 257 RESOLUTIONS: {
242 get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') }, 258 get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
@@ -244,6 +260,9 @@ const CONFIG = {
244 get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') }, 260 get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') },
245 get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') }, 261 get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') },
246 get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') } 262 get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') }
263 },
264 HLS: {
265 get ENABLED () { return config.get<boolean>('transcoding.hls.enabled') }
247 } 266 }
248 }, 267 },
249 IMPORT: { 268 IMPORT: {
@@ -280,6 +299,7 @@ const CONFIG = {
280 get SECURITYTXT_CONTACT () { return config.get<string>('admin.email') } 299 get SECURITYTXT_CONTACT () { return config.get<string>('admin.email') }
281 }, 300 },
282 SERVICES: { 301 SERVICES: {
302 get 'CSP-LOGGER' () { return config.get<string>('services.csp-logger') },
283 TWITTER: { 303 TWITTER: {
284 get USERNAME () { return config.get<string>('services.twitter.username') }, 304 get USERNAME () { return config.get<string>('services.twitter.username') },
285 get WHITELISTED () { return config.get<boolean>('services.twitter.whitelisted') } 305 get WHITELISTED () { return config.get<boolean>('services.twitter.whitelisted') }
@@ -289,27 +309,27 @@ const CONFIG = {
289 309
290// --------------------------------------------------------------------------- 310// ---------------------------------------------------------------------------
291 311
292const CONSTRAINTS_FIELDS = { 312let CONSTRAINTS_FIELDS = {
293 USERS: { 313 USERS: {
294 NAME: { min: 3, max: 120 }, // Length 314 NAME: { min: 1, max: 120 }, // Length
295 DESCRIPTION: { min: 3, max: 250 }, // Length 315 DESCRIPTION: { min: 3, max: 1000 }, // Length
296 USERNAME: { min: 3, max: 20 }, // Length 316 USERNAME: { min: 1, max: 50 }, // Length
297 PASSWORD: { min: 6, max: 255 }, // Length 317 PASSWORD: { min: 6, max: 255 }, // Length
298 VIDEO_QUOTA: { min: -1 }, 318 VIDEO_QUOTA: { min: -1 },
299 VIDEO_QUOTA_DAILY: { min: -1 }, 319 VIDEO_QUOTA_DAILY: { min: -1 },
300 BLOCKED_REASON: { min: 3, max: 250 } // Length 320 BLOCKED_REASON: { min: 3, max: 250 } // Length
301 }, 321 },
302 VIDEO_ABUSES: { 322 VIDEO_ABUSES: {
303 REASON: { min: 2, max: 300 }, // Length 323 REASON: { min: 2, max: 3000 }, // Length
304 MODERATION_COMMENT: { min: 2, max: 300 } // Length 324 MODERATION_COMMENT: { min: 2, max: 3000 } // Length
305 }, 325 },
306 VIDEO_BLACKLIST: { 326 VIDEO_BLACKLIST: {
307 REASON: { min: 2, max: 300 } // Length 327 REASON: { min: 2, max: 300 } // Length
308 }, 328 },
309 VIDEO_CHANNELS: { 329 VIDEO_CHANNELS: {
310 NAME: { min: 3, max: 120 }, // Length 330 NAME: { min: 1, max: 120 }, // Length
311 DESCRIPTION: { min: 3, max: 500 }, // Length 331 DESCRIPTION: { min: 3, max: 1000 }, // Length
312 SUPPORT: { min: 3, max: 500 }, // Length 332 SUPPORT: { min: 3, max: 1000 }, // Length
313 URL: { min: 3, max: 2000 } // Length 333 URL: { min: 3, max: 2000 } // Length
314 }, 334 },
315 VIDEO_CAPTIONS: { 335 VIDEO_CAPTIONS: {
@@ -333,19 +353,22 @@ const CONSTRAINTS_FIELDS = {
333 VIDEOS_REDUNDANCY: { 353 VIDEOS_REDUNDANCY: {
334 URL: { min: 3, max: 2000 } // Length 354 URL: { min: 3, max: 2000 } // Length
335 }, 355 },
356 VIDEO_RATES: {
357 URL: { min: 3, max: 2000 } // Length
358 },
336 VIDEOS: { 359 VIDEOS: {
337 NAME: { min: 3, max: 120 }, // Length 360 NAME: { min: 3, max: 120 }, // Length
338 LANGUAGE: { min: 1, max: 10 }, // Length 361 LANGUAGE: { min: 1, max: 10 }, // Length
339 TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length 362 TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length
340 DESCRIPTION: { min: 3, max: 10000 }, // Length 363 DESCRIPTION: { min: 3, max: 10000 }, // Length
341 SUPPORT: { min: 3, max: 500 }, // Length 364 SUPPORT: { min: 3, max: 1000 }, // Length
342 IMAGE: { 365 IMAGE: {
343 EXTNAME: [ '.jpg', '.jpeg' ], 366 EXTNAME: [ '.jpg', '.jpeg' ],
344 FILE_SIZE: { 367 FILE_SIZE: {
345 max: 2 * 1024 * 1024 // 2MB 368 max: 2 * 1024 * 1024 // 2MB
346 } 369 }
347 }, 370 },
348 EXTNAME: [ '.mp4', '.ogv', '.webm' ], 371 EXTNAME: buildVideosExtname(),
349 INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2 372 INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
350 DURATION: { min: 0 }, // Number 373 DURATION: { min: 0 }, // Number
351 TAGS: { min: 0, max: 5 }, // Number of total tags 374 TAGS: { min: 0, max: 5 }, // Number of total tags
@@ -378,6 +401,10 @@ const CONSTRAINTS_FIELDS = {
378 }, 401 },
379 VIDEO_SHARE: { 402 VIDEO_SHARE: {
380 URL: { min: 3, max: 2000 } // Length 403 URL: { min: 3, max: 2000 } // Length
404 },
405 CONTACT_FORM: {
406 FROM_NAME: { min: 1, max: 120 }, // Length
407 BODY: { min: 3, max: 5000 } // Length
381 } 408 }
382} 409}
383 410
@@ -393,7 +420,9 @@ const RATES_LIMIT = {
393} 420}
394 421
395let VIDEO_VIEW_LIFETIME = 60000 * 60 // 1 hour 422let VIDEO_VIEW_LIFETIME = 60000 * 60 // 1 hour
396const VIDEO_TRANSCODING_FPS = { 423let CONTACT_FORM_LIFETIME = 60000 * 60 // 1 hour
424
425const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = {
397 MIN: 10, 426 MIN: 10,
398 AVERAGE: 30, 427 AVERAGE: 30,
399 MAX: 60, 428 MAX: 60,
@@ -421,7 +450,7 @@ const VIDEO_CATEGORIES = {
421 8: 'People', 450 8: 'People',
422 9: 'Comedy', 451 9: 'Comedy',
423 10: 'Entertainment', 452 10: 'Entertainment',
424 11: 'News', 453 11: 'News & Politics',
425 12: 'How To', 454 12: 'How To',
426 13: 'Education', 455 13: 'Education',
427 14: 'Activism', 456 14: 'Activism',
@@ -468,27 +497,31 @@ const VIDEO_ABUSE_STATES = {
468 [VideoAbuseState.ACCEPTED]: 'Accepted' 497 [VideoAbuseState.ACCEPTED]: 'Accepted'
469} 498}
470 499
471const VIDEO_MIMETYPE_EXT = { 500const MIMETYPES = {
472 'video/webm': '.webm', 501 VIDEO: {
473 'video/ogg': '.ogv', 502 MIMETYPE_EXT: buildVideoMimetypeExt(),
474 'video/mp4': '.mp4' 503 EXT_MIMETYPE: null as { [ id: string ]: string }
475} 504 },
476const VIDEO_EXT_MIMETYPE = invert(VIDEO_MIMETYPE_EXT) 505 IMAGE: {
477 506 MIMETYPE_EXT: {
478const IMAGE_MIMETYPE_EXT = { 507 'image/png': '.png',
479 'image/png': '.png', 508 'image/jpg': '.jpg',
480 'image/jpg': '.jpg', 509 'image/jpeg': '.jpg'
481 'image/jpeg': '.jpg' 510 }
482} 511 },
483 512 VIDEO_CAPTIONS: {
484const VIDEO_CAPTIONS_MIMETYPE_EXT = { 513 MIMETYPE_EXT: {
485 'text/vtt': '.vtt', 514 'text/vtt': '.vtt',
486 'application/x-subrip': '.srt' 515 'application/x-subrip': '.srt'
487} 516 }
488 517 },
489const TORRENT_MIMETYPE_EXT = { 518 TORRENT: {
490 'application/x-bittorrent': '.torrent' 519 MIMETYPE_EXT: {
520 'application/x-bittorrent': '.torrent'
521 }
522 }
491} 523}
524MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT)
492 525
493// --------------------------------------------------------------------------- 526// ---------------------------------------------------------------------------
494 527
@@ -514,7 +547,7 @@ const ACTIVITY_PUB = {
514 COLLECTION_ITEMS_PER_PAGE: 10, 547 COLLECTION_ITEMS_PER_PAGE: 10,
515 FETCH_PAGE_LIMIT: 100, 548 FETCH_PAGE_LIMIT: 100,
516 URL_MIME_TYPES: { 549 URL_MIME_TYPES: {
517 VIDEO: Object.keys(VIDEO_MIMETYPE_EXT), 550 VIDEO: Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT),
518 TORRENT: [ 'application/x-bittorrent' ], 551 TORRENT: [ 'application/x-bittorrent' ],
519 MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ] 552 MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
520 }, 553 },
@@ -529,9 +562,15 @@ const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
529 APPLICATION: 'Application' 562 APPLICATION: 'Application'
530} 563}
531 564
565const HTTP_SIGNATURE = {
566 HEADER_NAME: 'signature',
567 ALGORITHM: 'rsa-sha256',
568 HEADERS_TO_SIGN: [ '(request-target)', 'host', 'date', 'digest' ]
569}
570
532// --------------------------------------------------------------------------- 571// ---------------------------------------------------------------------------
533 572
534const PRIVATE_RSA_KEY_SIZE = 2048 573let PRIVATE_RSA_KEY_SIZE = 2048
535 574
536// Password encryption 575// Password encryption
537const BCRYPT_SALT_SIZE = 10 576const BCRYPT_SALT_SIZE = 10
@@ -554,6 +593,10 @@ const STATIC_PATHS = {
554 THUMBNAILS: '/static/thumbnails/', 593 THUMBNAILS: '/static/thumbnails/',
555 TORRENTS: '/static/torrents/', 594 TORRENTS: '/static/torrents/',
556 WEBSEED: '/static/webseed/', 595 WEBSEED: '/static/webseed/',
596 REDUNDANCY: '/static/redundancy/',
597 PLAYLISTS: {
598 HLS: '/static/playlists/hls'
599 },
557 AVATARS: '/static/avatars/', 600 AVATARS: '/static/avatars/',
558 VIDEO_CAPTIONS: '/static/video-captions/' 601 VIDEO_CAPTIONS: '/static/video-captions/'
559} 602}
@@ -596,6 +639,9 @@ const CACHE = {
596 } 639 }
597} 640}
598 641
642const HLS_PLAYLIST_DIRECTORY = join(CONFIG.STORAGE.PLAYLISTS_DIR, 'hls')
643const HLS_REDUNDANCY_DIRECTORY = join(CONFIG.STORAGE.REDUNDANCY_DIR, 'hls')
644
599const MEMOIZE_TTL = { 645const MEMOIZE_TTL = {
600 OVERVIEWS_SAMPLE: 1000 * 3600 * 4 // 4 hours 646 OVERVIEWS_SAMPLE: 1000 * 3600 * 4 // 4 hours
601} 647}
@@ -635,6 +681,8 @@ const TRACKER_RATE_LIMITS = {
635 681
636// Special constants for a test instance 682// Special constants for a test instance
637if (isTestInstance() === true) { 683if (isTestInstance() === true) {
684 PRIVATE_RSA_KEY_SIZE = 1024
685
638 ACTOR_FOLLOW_SCORE.BASE = 20 686 ACTOR_FOLLOW_SCORE.BASE = 20
639 687
640 REMOTE_SCHEME.HTTP = 'http' 688 REMOTE_SCHEME.HTTP = 'http'
@@ -648,7 +696,7 @@ if (isTestInstance() === true) {
648 696
649 CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB 697 CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB
650 698
651 SCHEDULER_INTERVALS_MS.badActorFollow = 10000 699 SCHEDULER_INTERVALS_MS.actorFollowScores = 1000
652 SCHEDULER_INTERVALS_MS.removeOldJobs = 10000 700 SCHEDULER_INTERVALS_MS.removeOldJobs = 10000
653 SCHEDULER_INTERVALS_MS.updateVideos = 5000 701 SCHEDULER_INTERVALS_MS.updateVideos = 5000
654 REPEAT_JOBS['videos-views'] = { every: 5000 } 702 REPEAT_JOBS['videos-views'] = { every: 5000 }
@@ -656,21 +704,24 @@ if (isTestInstance() === true) {
656 REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 704 REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1
657 705
658 VIDEO_VIEW_LIFETIME = 1000 // 1 second 706 VIDEO_VIEW_LIFETIME = 1000 // 1 second
707 CONTACT_FORM_LIFETIME = 1000 // 1 second
659 708
660 JOB_ATTEMPTS['email'] = 1 709 JOB_ATTEMPTS['email'] = 1
661 710
662 CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000 711 CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000
663 MEMOIZE_TTL.OVERVIEWS_SAMPLE = 1 712 MEMOIZE_TTL.OVERVIEWS_SAMPLE = 1
664 ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms' 713 ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms'
714
715 RATES_LIMIT.LOGIN.MAX = 20
665} 716}
666 717
667updateWebserverConfig() 718updateWebserverUrls()
668 719
669// --------------------------------------------------------------------------- 720// ---------------------------------------------------------------------------
670 721
671export { 722export {
672 API_VERSION, 723 API_VERSION,
673 VIDEO_CAPTIONS_MIMETYPE_EXT, 724 HLS_REDUNDANCY_DIRECTORY,
674 AVATARS_SIZE, 725 AVATARS_SIZE,
675 ACCEPT_HEADERS, 726 ACCEPT_HEADERS,
676 BCRYPT_SALT_SIZE, 727 BCRYPT_SALT_SIZE,
@@ -695,10 +746,10 @@ export {
695 PRIVATE_RSA_KEY_SIZE, 746 PRIVATE_RSA_KEY_SIZE,
696 ROUTE_CACHE_LIFETIME, 747 ROUTE_CACHE_LIFETIME,
697 SORTABLE_COLUMNS, 748 SORTABLE_COLUMNS,
749 HLS_PLAYLIST_DIRECTORY,
698 FEEDS, 750 FEEDS,
699 JOB_TTL, 751 JOB_TTL,
700 NSFW_POLICY_TYPES, 752 NSFW_POLICY_TYPES,
701 TORRENT_MIMETYPE_EXT,
702 STATIC_MAX_AGE, 753 STATIC_MAX_AGE,
703 STATIC_PATHS, 754 STATIC_PATHS,
704 VIDEO_IMPORT_TIMEOUT, 755 VIDEO_IMPORT_TIMEOUT,
@@ -711,7 +762,6 @@ export {
711 VIDEO_LICENCES, 762 VIDEO_LICENCES,
712 VIDEO_STATES, 763 VIDEO_STATES,
713 VIDEO_RATE_TYPES, 764 VIDEO_RATE_TYPES,
714 VIDEO_MIMETYPE_EXT,
715 VIDEO_TRANSCODING_FPS, 765 VIDEO_TRANSCODING_FPS,
716 FFMPEG_NICE, 766 FFMPEG_NICE,
717 VIDEO_ABUSE_STATES, 767 VIDEO_ABUSE_STATES,
@@ -719,17 +769,18 @@ export {
719 USER_PASSWORD_RESET_LIFETIME, 769 USER_PASSWORD_RESET_LIFETIME,
720 MEMOIZE_TTL, 770 MEMOIZE_TTL,
721 USER_EMAIL_VERIFY_LIFETIME, 771 USER_EMAIL_VERIFY_LIFETIME,
722 IMAGE_MIMETYPE_EXT,
723 OVERVIEWS, 772 OVERVIEWS,
724 SCHEDULER_INTERVALS_MS, 773 SCHEDULER_INTERVALS_MS,
725 REPEAT_JOBS, 774 REPEAT_JOBS,
726 STATIC_DOWNLOAD_PATHS, 775 STATIC_DOWNLOAD_PATHS,
727 RATES_LIMIT, 776 RATES_LIMIT,
728 VIDEO_EXT_MIMETYPE, 777 MIMETYPES,
729 CRAWL_REQUEST_CONCURRENCY, 778 CRAWL_REQUEST_CONCURRENCY,
730 JOB_COMPLETED_LIFETIME, 779 JOB_COMPLETED_LIFETIME,
780 HTTP_SIGNATURE,
731 VIDEO_IMPORT_STATES, 781 VIDEO_IMPORT_STATES,
732 VIDEO_VIEW_LIFETIME, 782 VIDEO_VIEW_LIFETIME,
783 CONTACT_FORM_LIFETIME,
733 buildLanguages 784 buildLanguages
734} 785}
735 786
@@ -746,16 +797,50 @@ function getLocalConfigFilePath () {
746 return join(dirname(configSources[ 0 ].name), filename + '.json') 797 return join(dirname(configSources[ 0 ].name), filename + '.json')
747} 798}
748 799
749function updateWebserverConfig () { 800function buildVideoMimetypeExt () {
801 const data = {
802 'video/webm': '.webm',
803 'video/ogg': '.ogv',
804 'video/mp4': '.mp4'
805 }
806
807 if (CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS) {
808 Object.assign(data, {
809 'video/quicktime': '.mov',
810 'video/x-msvideo': '.avi',
811 'video/x-flv': '.flv',
812 'video/x-matroska': '.mkv',
813 'application/octet-stream': '.mkv',
814 'video/avi': '.avi'
815 })
816 }
817
818 return data
819}
820
821function updateWebserverUrls () {
750 CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT) 822 CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT)
751 CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP) 823 CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP)
752} 824}
753 825
826function updateWebserverConfig () {
827 CONSTRAINTS_FIELDS.VIDEOS.EXTNAME = buildVideosExtname()
828
829 MIMETYPES.VIDEO.MIMETYPE_EXT = buildVideoMimetypeExt()
830 MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT)
831}
832
833function buildVideosExtname () {
834 return CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS
835 ? [ '.mp4', '.ogv', '.webm', '.mkv', '.mov', '.avi', '.flv' ]
836 : [ '.mp4', '.ogv', '.webm' ]
837}
838
754function buildVideosRedundancy (objs: any[]): VideosRedundancy[] { 839function buildVideosRedundancy (objs: any[]): VideosRedundancy[] {
755 if (!objs) return [] 840 if (!objs) return []
756 841
757 return objs.map(obj => { 842 return objs.map(obj => {
758 return Object.assign(obj, { 843 return Object.assign({}, obj, {
759 minLifetime: parseDuration(obj.min_lifetime), 844 minLifetime: parseDuration(obj.min_lifetime),
760 size: bytes.parse(obj.size), 845 size: bytes.parse(obj.size),
761 minViews: obj.min_views 846 minViews: obj.min_views
@@ -832,4 +917,5 @@ export function reloadConfig () {
832 config = require('config') 917 config = require('config')
833 918
834 updateWebserverConfig() 919 updateWebserverConfig()
920 updateWebserverUrls()
835} 921}
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index 482c03b31..fe296142d 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -29,6 +29,11 @@ import { VideoViewModel } from '../models/video/video-views'
29import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership' 29import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership'
30import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' 30import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
31import { UserVideoHistoryModel } from '../models/account/user-video-history' 31import { UserVideoHistoryModel } from '../models/account/user-video-history'
32import { AccountBlocklistModel } from '../models/account/account-blocklist'
33import { ServerBlocklistModel } from '../models/server/server-blocklist'
34import { UserNotificationModel } from '../models/account/user-notification'
35import { UserNotificationSettingModel } from '../models/account/user-notification-setting'
36import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
32 37
33require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string 38require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string
34 39
@@ -91,7 +96,12 @@ async function initDatabaseModels (silent: boolean) {
91 VideoImportModel, 96 VideoImportModel,
92 VideoViewModel, 97 VideoViewModel,
93 VideoRedundancyModel, 98 VideoRedundancyModel,
94 UserVideoHistoryModel 99 UserVideoHistoryModel,
100 AccountBlocklistModel,
101 ServerBlocklistModel,
102 UserNotificationModel,
103 UserNotificationSettingModel,
104 VideoStreamingPlaylistModel
95 ]) 105 ])
96 106
97 // Check extensions exist in the database 107 // Check extensions exist in the database
@@ -115,25 +125,27 @@ export {
115// --------------------------------------------------------------------------- 125// ---------------------------------------------------------------------------
116 126
117async function checkPostgresExtensions () { 127async function checkPostgresExtensions () {
118 const extensions = [ 128 const promises = [
119 'pg_trgm', 129 checkPostgresExtension('pg_trgm'),
120 'unaccent' 130 checkPostgresExtension('unaccent')
121 ] 131 ]
122 132
123 for (const extension of extensions) { 133 return Promise.all(promises)
124 const query = `SELECT true AS enabled FROM pg_available_extensions WHERE name = '${extension}' AND installed_version IS NOT NULL;` 134}
125 const [ res ] = await sequelizeTypescript.query(query, { raw: true }) 135
136async function checkPostgresExtension (extension: string) {
137 const query = `SELECT true AS enabled FROM pg_available_extensions WHERE name = '${extension}' AND installed_version IS NOT NULL;`
138 const [ res ] = await sequelizeTypescript.query(query, { raw: true })
126 139
127 if (!res || res.length === 0 || res[ 0 ][ 'enabled' ] !== true) { 140 if (!res || res.length === 0 || res[ 0 ][ 'enabled' ] !== true) {
128 // Try to create the extension ourself 141 // Try to create the extension ourself
129 try { 142 try {
130 await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true }) 143 await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true })
131 144
132 } catch { 145 } catch {
133 const errorMessage = `You need to enable ${extension} extension in PostgreSQL. ` + 146 const errorMessage = `You need to enable ${extension} extension in PostgreSQL. ` +
134 `You can do so by running 'CREATE EXTENSION ${extension};' as a PostgreSQL super user in ${CONFIG.DATABASE.DBNAME} database.` 147 `You can do so by running 'CREATE EXTENSION ${extension};' as a PostgreSQL super user in ${CONFIG.DATABASE.DBNAME} database.`
135 throw new Error(errorMessage) 148 throw new Error(errorMessage)
136 }
137 } 149 }
138 } 150 }
139} 151}
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts
index c952ad46c..2b22e16fe 100644
--- a/server/initializers/installer.ts
+++ b/server/initializers/installer.ts
@@ -6,18 +6,27 @@ import { UserModel } from '../models/account/user'
6import { ApplicationModel } from '../models/application/application' 6import { ApplicationModel } from '../models/application/application'
7import { OAuthClientModel } from '../models/oauth/oauth-client' 7import { OAuthClientModel } from '../models/oauth/oauth-client'
8import { applicationExist, clientsExist, usersExist } from './checker-after-init' 8import { applicationExist, clientsExist, usersExist } from './checker-after-init'
9import { CACHE, CONFIG, LAST_MIGRATION_VERSION } from './constants' 9import { CACHE, CONFIG, HLS_PLAYLIST_DIRECTORY, LAST_MIGRATION_VERSION } from './constants'
10import { sequelizeTypescript } from './database' 10import { sequelizeTypescript } from './database'
11import { remove, ensureDir } from 'fs-extra' 11import { remove, ensureDir } from 'fs-extra'
12 12
13async function installApplication () { 13async function installApplication () {
14 try { 14 try {
15 await sequelizeTypescript.sync() 15 await Promise.all([
16 await removeCacheDirectories() 16 // Database related
17 await createDirectoriesIfNotExist() 17 sequelizeTypescript.sync()
18 await createApplicationIfNotExist() 18 .then(() => {
19 await createOAuthClientIfNotExist() 19 return Promise.all([
20 await createOAuthAdminIfNotExist() 20 createApplicationIfNotExist(),
21 createOAuthClientIfNotExist(),
22 createOAuthAdminIfNotExist()
23 ])
24 }),
25
26 // Directories
27 removeCacheDirectories()
28 .then(() => createDirectoriesIfNotExist())
29 ])
21 } catch (err) { 30 } catch (err) {
22 logger.error('Cannot install application.', { err }) 31 logger.error('Cannot install application.', { err })
23 process.exit(-1) 32 process.exit(-1)
@@ -64,6 +73,9 @@ function createDirectoriesIfNotExist () {
64 tasks.push(ensureDir(dir)) 73 tasks.push(ensureDir(dir))
65 } 74 }
66 75
76 // Playlist directories
77 tasks.push(ensureDir(HLS_PLAYLIST_DIRECTORY))
78
67 return Promise.all(tasks) 79 return Promise.all(tasks)
68} 80}
69 81
diff --git a/server/initializers/migrations/0120-video-null.ts b/server/initializers/migrations/0120-video-null.ts
index 63f3984dd..6d253f04f 100644
--- a/server/initializers/migrations/0120-video-null.ts
+++ b/server/initializers/migrations/0120-video-null.ts
@@ -1,5 +1,4 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../constants'
3 2
4async function up (utils: { 3async function up (utils: {
5 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction,
@@ -28,7 +27,7 @@ async function up (utils: {
28 27
29 { 28 {
30 const data = { 29 const data = {
31 type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max), 30 type: Sequelize.STRING(10000),
32 allowNull: true, 31 allowNull: true,
33 defaultValue: null 32 defaultValue: null
34 } 33 }
diff --git a/server/initializers/migrations/0195-support.ts b/server/initializers/migrations/0195-support.ts
index 8722a5f22..3b9eabe79 100644
--- a/server/initializers/migrations/0195-support.ts
+++ b/server/initializers/migrations/0195-support.ts
@@ -1,5 +1,4 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../index'
3 2
4async function up (utils: { 3async function up (utils: {
5 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction,
@@ -8,7 +7,7 @@ async function up (utils: {
8}): Promise<void> { 7}): Promise<void> {
9 { 8 {
10 const data = { 9 const data = {
11 type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEOS.SUPPORT.max), 10 type: Sequelize.STRING(500),
12 allowNull: true, 11 allowNull: true,
13 defaultValue: null 12 defaultValue: null
14 } 13 }
@@ -17,7 +16,7 @@ async function up (utils: {
17 16
18 { 17 {
19 const data = { 18 const data = {
20 type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.SUPPORT.max), 19 type: Sequelize.STRING(500),
21 allowNull: true, 20 allowNull: true,
22 defaultValue: null 21 defaultValue: null
23 } 22 }
@@ -26,7 +25,7 @@ async function up (utils: {
26 25
27 { 26 {
28 const data = { 27 const data = {
29 type: Sequelize.STRING(CONSTRAINTS_FIELDS.USERS.DESCRIPTION.max), 28 type: Sequelize.STRING(250),
30 allowNull: true, 29 allowNull: true,
31 defaultValue: null 30 defaultValue: null
32 } 31 }
@@ -35,7 +34,7 @@ async function up (utils: {
35 34
36 { 35 {
37 const data = { 36 const data = {
38 type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max), 37 type: Sequelize.STRING(10000),
39 allowNull: true, 38 allowNull: true,
40 defaultValue: null 39 defaultValue: null
41 } 40 }
diff --git a/server/initializers/migrations/0245-user-blocked.ts b/server/initializers/migrations/0245-user-blocked.ts
index 5a04ecd2b..19c7d5b9c 100644
--- a/server/initializers/migrations/0245-user-blocked.ts
+++ b/server/initializers/migrations/0245-user-blocked.ts
@@ -1,5 +1,4 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../constants'
3 2
4async function up (utils: { 3async function up (utils: {
5 transaction: Sequelize.Transaction 4 transaction: Sequelize.Transaction
@@ -31,7 +30,7 @@ async function up (utils: {
31 30
32 { 31 {
33 const data = { 32 const data = {
34 type: Sequelize.STRING(CONSTRAINTS_FIELDS.USERS.BLOCKED_REASON.max), 33 type: Sequelize.STRING(250),
35 allowNull: true, 34 allowNull: true,
36 defaultValue: null 35 defaultValue: null
37 } 36 }
diff --git a/server/initializers/migrations/0250-video-abuse-state.ts b/server/initializers/migrations/0250-video-abuse-state.ts
index acb668ae1..50de25182 100644
--- a/server/initializers/migrations/0250-video-abuse-state.ts
+++ b/server/initializers/migrations/0250-video-abuse-state.ts
@@ -1,5 +1,4 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../constants'
3import { VideoAbuseState } from '../../../shared/models/videos' 2import { VideoAbuseState } from '../../../shared/models/videos'
4 3
5async function up (utils: { 4async function up (utils: {
@@ -32,7 +31,7 @@ async function up (utils: {
32 31
33 { 32 {
34 const data = { 33 const data = {
35 type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max), 34 type: Sequelize.STRING(300),
36 allowNull: true, 35 allowNull: true,
37 defaultValue: null 36 defaultValue: null
38 } 37 }
diff --git a/server/initializers/migrations/0255-video-blacklist-reason.ts b/server/initializers/migrations/0255-video-blacklist-reason.ts
index a380e620e..69d6efb9e 100644
--- a/server/initializers/migrations/0255-video-blacklist-reason.ts
+++ b/server/initializers/migrations/0255-video-blacklist-reason.ts
@@ -1,5 +1,4 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../constants'
3import { VideoAbuseState } from '../../../shared/models/videos' 2import { VideoAbuseState } from '../../../shared/models/videos'
4 3
5async function up (utils: { 4async function up (utils: {
@@ -10,7 +9,7 @@ async function up (utils: {
10 9
11 { 10 {
12 const data = { 11 const data = {
13 type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEO_BLACKLIST.REASON.max), 12 type: Sequelize.STRING(300),
14 allowNull: true, 13 allowNull: true,
15 defaultValue: null 14 defaultValue: null
16 } 15 }
diff --git a/server/initializers/migrations/0260-upload-quota-daily.ts b/server/initializers/migrations/0260-upload-quota-daily.ts
index d25154ba6..cbbe391ef 100644
--- a/server/initializers/migrations/0260-upload-quota-daily.ts
+++ b/server/initializers/migrations/0260-upload-quota-daily.ts
@@ -1,5 +1,4 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../constants'
3 2
4async function up (utils: { 3async function up (utils: {
5 transaction: Sequelize.Transaction 4 transaction: Sequelize.Transaction
diff --git a/server/initializers/migrations/0275-video-file-unique.ts b/server/initializers/migrations/0275-video-file-unique.ts
index fd89188c0..e321ecb04 100644
--- a/server/initializers/migrations/0275-video-file-unique.ts
+++ b/server/initializers/migrations/0275-video-file-unique.ts
@@ -5,6 +5,12 @@ async function up (utils: {
5 queryInterface: Sequelize.QueryInterface 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<any> { 7}): Promise<any> {
8 // Delete duplicated keys
9 {
10 const query = 'DELETE FROM "server" s1 USING "server" s2 WHERE s1.id < s2.id AND s1."host" = s2."host"'
11 await utils.sequelize.query(query)
12 }
13
8 { 14 {
9 const query = 'DELETE FROM "videoFile" vf1 USING "videoFile" vf2 WHERE vf1.id < vf2.id ' + 15 const query = 'DELETE FROM "videoFile" vf1 USING "videoFile" vf2 WHERE vf1.id < vf2.id ' +
10 'AND vf1."videoId" = vf2."videoId" AND vf1.resolution = vf2.resolution AND vf1.fps IS NULL' 16 'AND vf1."videoId" = vf2."videoId" AND vf1.resolution = vf2.resolution AND vf1.fps IS NULL'
diff --git a/server/initializers/migrations/0280-webtorrent-policy-user.ts b/server/initializers/migrations/0280-webtorrent-policy-user.ts
new file mode 100644
index 000000000..e6488356a
--- /dev/null
+++ b/server/initializers/migrations/0280-webtorrent-policy-user.ts
@@ -0,0 +1,28 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize
7}): Promise<any> {
8 {
9 const data = {
10 type: Sequelize.BOOLEAN,
11 allowNull: false,
12 defaultValue: true
13 }
14
15 await utils.queryInterface.addColumn('user', 'webTorrentEnabled', data)
16 }
17
18}
19
20async function down (utils: {
21 transaction: Sequelize.Transaction
22 queryInterface: Sequelize.QueryInterface
23 sequelize: Sequelize.Sequelize
24}): Promise<any> {
25 await utils.queryInterface.removeColumn('user', 'webTorrentEnabled')
26}
27
28export { up, down }
diff --git a/server/initializers/migrations/0285-description-support.ts b/server/initializers/migrations/0285-description-support.ts
new file mode 100644
index 000000000..85ef4ef39
--- /dev/null
+++ b/server/initializers/migrations/0285-description-support.ts
@@ -0,0 +1,53 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize,
7 db: any
8}): Promise<void> {
9 {
10 const data = {
11 type: Sequelize.STRING(1000),
12 allowNull: true,
13 defaultValue: null
14 }
15 await utils.queryInterface.changeColumn('video', 'support', data)
16 }
17
18 {
19 const data = {
20 type: Sequelize.STRING(1000),
21 allowNull: true,
22 defaultValue: null
23 }
24 await utils.queryInterface.changeColumn('videoChannel', 'support', data)
25 }
26
27 {
28 const data = {
29 type: Sequelize.STRING(1000),
30 allowNull: true,
31 defaultValue: null
32 }
33 await utils.queryInterface.changeColumn('videoChannel', 'description', data)
34 }
35
36 {
37 const data = {
38 type: Sequelize.STRING(1000),
39 allowNull: true,
40 defaultValue: null
41 }
42 await utils.queryInterface.changeColumn('account', 'description', data)
43 }
44}
45
46function down (options) {
47 throw new Error('Not implemented.')
48}
49
50export {
51 up,
52 down
53}
diff --git a/server/initializers/migrations/0290-account-video-rate-url.ts b/server/initializers/migrations/0290-account-video-rate-url.ts
new file mode 100644
index 000000000..bdabf2929
--- /dev/null
+++ b/server/initializers/migrations/0290-account-video-rate-url.ts
@@ -0,0 +1,46 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize,
7 db: any
8}): Promise<void> {
9 {
10 const data = {
11 type: Sequelize.STRING(2000),
12 allowNull: true
13 }
14
15 await utils.queryInterface.addColumn('accountVideoRate', 'url', data)
16 }
17
18 {
19 const builtUrlQuery = `SELECT "actor"."url" || '/' || "accountVideoRate"."type" || 's/' || "videoId" ` +
20 'FROM "accountVideoRate" ' +
21 'INNER JOIN account ON account.id = "accountVideoRate"."accountId" ' +
22 'INNER JOIN actor ON actor.id = account."actorId" ' +
23 'WHERE "base".id = "accountVideoRate".id'
24
25 const query = 'UPDATE "accountVideoRate" base SET "url" = (' + builtUrlQuery + ') WHERE "url" IS NULL'
26 await utils.sequelize.query(query)
27 }
28
29 {
30 const data = {
31 type: Sequelize.STRING(2000),
32 allowNull: false,
33 defaultValue: null
34 }
35 await utils.queryInterface.changeColumn('accountVideoRate', 'url', data)
36 }
37}
38
39function down (options) {
40 throw new Error('Not implemented.')
41}
42
43export {
44 up,
45 down
46}
diff --git a/server/initializers/migrations/0295-video-file-extname.ts b/server/initializers/migrations/0295-video-file-extname.ts
new file mode 100644
index 000000000..dbf249f66
--- /dev/null
+++ b/server/initializers/migrations/0295-video-file-extname.ts
@@ -0,0 +1,49 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize,
7 db: any
8}): Promise<void> {
9 {
10 await utils.queryInterface.renameColumn('videoFile', 'extname', 'extname_old')
11 }
12
13 {
14 const data = {
15 type: Sequelize.STRING,
16 defaultValue: null,
17 allowNull: true
18 }
19
20 await utils.queryInterface.addColumn('videoFile', 'extname', data)
21 }
22
23 {
24 const query = 'UPDATE "videoFile" SET "extname" = "extname_old"::text'
25 await utils.sequelize.query(query)
26 }
27
28 {
29 const data = {
30 type: Sequelize.STRING,
31 defaultValue: null,
32 allowNull: false
33 }
34 await utils.queryInterface.changeColumn('videoFile', 'extname', data)
35 }
36
37 {
38 await utils.queryInterface.removeColumn('videoFile', 'extname_old')
39 }
40}
41
42function down (options) {
43 throw new Error('Not implemented.')
44}
45
46export {
47 up,
48 down
49}
diff --git a/server/initializers/migrations/0300-user-videos-history-enabled.ts b/server/initializers/migrations/0300-user-videos-history-enabled.ts
new file mode 100644
index 000000000..aa5fc21fb
--- /dev/null
+++ b/server/initializers/migrations/0300-user-videos-history-enabled.ts
@@ -0,0 +1,27 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize,
7 db: any
8}): Promise<void> {
9 {
10 const data = {
11 type: Sequelize.BOOLEAN,
12 allowNull: false,
13 defaultValue: true
14 }
15
16 await utils.queryInterface.addColumn('user', 'videosHistoryEnabled', data)
17 }
18}
19
20function down (options) {
21 throw new Error('Not implemented.')
22}
23
24export {
25 up,
26 down
27}
diff --git a/server/initializers/migrations/0305-fix-unfederated-videos.ts b/server/initializers/migrations/0305-fix-unfederated-videos.ts
new file mode 100644
index 000000000..be206601f
--- /dev/null
+++ b/server/initializers/migrations/0305-fix-unfederated-videos.ts
@@ -0,0 +1,52 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize,
7 db: any
8}): Promise<void> {
9 {
10 const query = `INSERT INTO "videoShare" (url, "actorId", "videoId", "createdAt", "updatedAt") ` +
11 `(` +
12 `SELECT ` +
13 `video.url || '/announces/' || "videoChannel"."actorId" as url, ` +
14 `"videoChannel"."actorId" AS "actorId", ` +
15 `"video"."id" AS "videoId", ` +
16 `NOW() AS "createdAt", ` +
17 `NOW() AS "updatedAt" ` +
18 `FROM video ` +
19 `INNER JOIN "videoChannel" ON "video"."channelId" = "videoChannel"."id" ` +
20 `WHERE "video"."remote" = false AND "video"."privacy" != 3 AND "video"."state" = 1` +
21 `) ` +
22 `ON CONFLICT DO NOTHING`
23
24 await utils.sequelize.query(query)
25 }
26
27 {
28 const query = `INSERT INTO "videoShare" (url, "actorId", "videoId", "createdAt", "updatedAt") ` +
29 `(` +
30 `SELECT ` +
31 `video.url || '/announces/' || (SELECT id FROM actor WHERE "preferredUsername" = 'peertube' ORDER BY id ASC LIMIT 1) as url, ` +
32 `(SELECT id FROM actor WHERE "preferredUsername" = 'peertube' ORDER BY id ASC LIMIT 1) AS "actorId", ` +
33 `"video"."id" AS "videoId", ` +
34 `NOW() AS "createdAt", ` +
35 `NOW() AS "updatedAt" ` +
36 `FROM video ` +
37 `WHERE "video"."remote" = false AND "video"."privacy" != 3 AND "video"."state" = 1` +
38 `) ` +
39 `ON CONFLICT DO NOTHING`
40
41 await utils.sequelize.query(query)
42 }
43}
44
45function down (options) {
46 throw new Error('Not implemented.')
47}
48
49export {
50 up,
51 down
52}
diff --git a/server/initializers/migrations/0310-drop-unused-video-indexes.ts b/server/initializers/migrations/0310-drop-unused-video-indexes.ts
new file mode 100644
index 000000000..d51f430c0
--- /dev/null
+++ b/server/initializers/migrations/0310-drop-unused-video-indexes.ts
@@ -0,0 +1,32 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize,
7 db: any
8}): Promise<void> {
9 const indexNames = [
10 'video_category',
11 'video_licence',
12 'video_nsfw',
13 'video_language',
14 'video_wait_transcoding',
15 'video_state',
16 'video_remote',
17 'video_likes'
18 ]
19
20 for (const indexName of indexNames) {
21 await utils.sequelize.query('DROP INDEX IF EXISTS "' + indexName + '";')
22 }
23}
24
25function down (options) {
26 throw new Error('Not implemented.')
27}
28
29export {
30 up,
31 down
32}
diff --git a/server/initializers/migrations/0315-user-notifications.ts b/server/initializers/migrations/0315-user-notifications.ts
new file mode 100644
index 000000000..8284c58a0
--- /dev/null
+++ b/server/initializers/migrations/0315-user-notifications.ts
@@ -0,0 +1,47 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize
7}): Promise<void> {
8
9 {
10 const query = `
11CREATE TABLE IF NOT EXISTS "userNotificationSetting" ("id" SERIAL,
12"newVideoFromSubscription" INTEGER NOT NULL DEFAULT NULL,
13"newCommentOnMyVideo" INTEGER NOT NULL DEFAULT NULL,
14"videoAbuseAsModerator" INTEGER NOT NULL DEFAULT NULL,
15"blacklistOnMyVideo" INTEGER NOT NULL DEFAULT NULL,
16"myVideoPublished" INTEGER NOT NULL DEFAULT NULL,
17"myVideoImportFinished" INTEGER NOT NULL DEFAULT NULL,
18"newUserRegistration" INTEGER NOT NULL DEFAULT NULL,
19"newFollow" INTEGER NOT NULL DEFAULT NULL,
20"commentMention" INTEGER NOT NULL DEFAULT NULL,
21"userId" INTEGER REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
22"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
23"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
24PRIMARY KEY ("id"))
25`
26 await utils.sequelize.query(query)
27 }
28
29 {
30 const query = 'INSERT INTO "userNotificationSetting" ' +
31 '("newVideoFromSubscription", "newCommentOnMyVideo", "videoAbuseAsModerator", "blacklistOnMyVideo", ' +
32 '"myVideoPublished", "myVideoImportFinished", "newUserRegistration", "newFollow", "commentMention", ' +
33 '"userId", "createdAt", "updatedAt") ' +
34 '(SELECT 1, 1, 3, 3, 1, 1, 1, 1, 1, id, NOW(), NOW() FROM "user")'
35
36 await utils.sequelize.query(query)
37 }
38}
39
40function down (options) {
41 throw new Error('Not implemented.')
42}
43
44export {
45 up,
46 down
47}
diff --git a/server/initializers/migrations/0320-blacklist-unfederate.ts b/server/initializers/migrations/0320-blacklist-unfederate.ts
new file mode 100644
index 000000000..6fb7bbb90
--- /dev/null
+++ b/server/initializers/migrations/0320-blacklist-unfederate.ts
@@ -0,0 +1,27 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize
7}): Promise<void> {
8
9 {
10 const data = {
11 type: Sequelize.BOOLEAN,
12 allowNull: false,
13 defaultValue: false
14 }
15
16 await utils.queryInterface.addColumn('videoBlacklist', 'unfederated', data)
17 }
18}
19
20function down (options) {
21 throw new Error('Not implemented.')
22}
23
24export {
25 up,
26 down
27}
diff --git a/server/initializers/migrations/0325-video-abuse-fields.ts b/server/initializers/migrations/0325-video-abuse-fields.ts
new file mode 100644
index 000000000..fca6d666f
--- /dev/null
+++ b/server/initializers/migrations/0325-video-abuse-fields.ts
@@ -0,0 +1,37 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize
7}): Promise<void> {
8
9 {
10 const data = {
11 type: Sequelize.STRING(3000),
12 allowNull: false,
13 defaultValue: null
14 }
15
16 await utils.queryInterface.changeColumn('videoAbuse', 'reason', data)
17 }
18
19 {
20 const data = {
21 type: Sequelize.STRING(3000),
22 allowNull: true,
23 defaultValue: null
24 }
25
26 await utils.queryInterface.changeColumn('videoAbuse', 'moderationComment', data)
27 }
28}
29
30function down (options) {
31 throw new Error('Not implemented.')
32}
33
34export {
35 up,
36 down
37}
diff --git a/server/initializers/migrations/0330-video-streaming-playlist.ts b/server/initializers/migrations/0330-video-streaming-playlist.ts
new file mode 100644
index 000000000..c85a762ab
--- /dev/null
+++ b/server/initializers/migrations/0330-video-streaming-playlist.ts
@@ -0,0 +1,51 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize
7}): Promise<void> {
8
9 {
10 const query = `
11 CREATE TABLE IF NOT EXISTS "videoStreamingPlaylist"
12(
13 "id" SERIAL,
14 "type" INTEGER NOT NULL,
15 "playlistUrl" VARCHAR(2000) NOT NULL,
16 "p2pMediaLoaderInfohashes" VARCHAR(255)[] NOT NULL,
17 "segmentsSha256Url" VARCHAR(255) NOT NULL,
18 "videoId" INTEGER NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
19 "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
20 "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
21 PRIMARY KEY ("id")
22);`
23 await utils.sequelize.query(query)
24 }
25
26 {
27 const data = {
28 type: Sequelize.INTEGER,
29 allowNull: true,
30 defaultValue: null
31 }
32
33 await utils.queryInterface.changeColumn('videoRedundancy', 'videoFileId', data)
34 }
35
36 {
37 const query = 'ALTER TABLE "videoRedundancy" ADD COLUMN "videoStreamingPlaylistId" INTEGER NULL ' +
38 'REFERENCES "videoStreamingPlaylist" ("id") ON DELETE CASCADE ON UPDATE CASCADE'
39
40 await utils.sequelize.query(query)
41 }
42}
43
44function down (options) {
45 throw new Error('Not implemented.')
46}
47
48export {
49 up,
50 down
51}