aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/initializers
diff options
context:
space:
mode:
Diffstat (limited to 'server/initializers')
-rw-r--r--server/initializers/checker-after-init.ts13
-rw-r--r--server/initializers/checker-before-init.ts17
-rw-r--r--server/initializers/config.ts277
-rw-r--r--server/initializers/constants.ts381
-rw-r--r--server/initializers/database.ts31
-rw-r--r--server/initializers/index.ts2
-rw-r--r--server/initializers/installer.ts26
-rw-r--r--server/initializers/migrations/0075-video-resolutions.ts2
-rw-r--r--server/initializers/migrations/0080-video-channels.ts4
-rw-r--r--server/initializers/migrations/0100-activitypub.ts10
-rw-r--r--server/initializers/migrations/0135-video-channel-actor.ts3
-rw-r--r--server/initializers/migrations/0140-actor-url.ts6
-rw-r--r--server/initializers/migrations/0170-actor-follow-score.ts2
-rw-r--r--server/initializers/migrations/0210-video-language.ts2
-rw-r--r--server/initializers/migrations/0215-video-support-length.ts1
-rw-r--r--server/initializers/migrations/0235-delete-some-video-indexes.ts4
-rw-r--r--server/initializers/migrations/0240-drop-old-indexes.ts4
-rw-r--r--server/initializers/migrations/0330-video-streaming-playlist.ts51
-rw-r--r--server/initializers/migrations/0335-video-downloading-enabled.ts27
-rw-r--r--server/initializers/migrations/0340-add-originally-published-at.ts25
-rw-r--r--server/initializers/migrations/0345-video-playlists.ts88
-rw-r--r--server/initializers/migrations/0350-video-blacklist-type.ts64
-rw-r--r--server/initializers/migrations/0355-p2p-peer-version.ts41
-rw-r--r--server/initializers/migrations/0360-notification-instance-follower.ts40
-rw-r--r--server/initializers/migrations/0365-user-admin-flags.ts40
-rw-r--r--server/initializers/migrations/0370-thumbnail.ts50
-rw-r--r--server/initializers/migrations/0375-account-description.ts25
-rw-r--r--server/initializers/migrator.ts8
28 files changed, 939 insertions, 305 deletions
diff --git a/server/initializers/checker-after-init.ts b/server/initializers/checker-after-init.ts
index 955d55206..db3115085 100644
--- a/server/initializers/checker-after-init.ts
+++ b/server/initializers/checker-after-init.ts
@@ -4,19 +4,20 @@ import { UserModel } from '../models/account/user'
4import { ApplicationModel } from '../models/application/application' 4import { ApplicationModel } from '../models/application/application'
5import { OAuthClientModel } from '../models/oauth/oauth-client' 5import { OAuthClientModel } from '../models/oauth/oauth-client'
6import { parse } from 'url' 6import { parse } from 'url'
7import { CONFIG } from './constants' 7import { CONFIG } from './config'
8import { logger } from '../helpers/logger' 8import { logger } from '../helpers/logger'
9import { getServerActor } from '../helpers/utils' 9import { 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' 13import { Emailer } from '../lib/emailer'
14import { WEBSERVER } from './constants'
14 15
15async function checkActivityPubUrls () { 16async function checkActivityPubUrls () {
16 const actor = await getServerActor() 17 const actor = await getServerActor()
17 18
18 const parsed = parse(actor.url) 19 const parsed = parse(actor.url)
19 if (CONFIG.WEBSERVER.HOST !== parsed.host) { 20 if (WEBSERVER.HOST !== parsed.host) {
20 const NODE_ENV = config.util.getEnv('NODE_ENV') 21 const NODE_ENV = config.util.getEnv('NODE_ENV')
21 const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR') 22 const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR')
22 23
@@ -34,6 +35,12 @@ async function checkActivityPubUrls () {
34// Return an error message, or null if everything is okay 35// Return an error message, or null if everything is okay
35function checkConfig () { 36function checkConfig () {
36 37
38 // Moved configuration keys
39 if (config.has('services.csp-logger')) {
40 logger.warn('services.csp-logger configuration has been renamed to csp.report_uri. Please update your configuration file.')
41 }
42
43 // Email verification
37 if (!Emailer.isEnabled()) { 44 if (!Emailer.isEnabled()) {
38 if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { 45 if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
39 return 'Emailer is disabled but you require signup email verification.' 46 return 'Emailer is disabled but you require signup email verification.'
@@ -77,6 +84,8 @@ function checkConfig () {
77 if (recentlyAddedStrategy && isNaN(recentlyAddedStrategy.minViews)) { 84 if (recentlyAddedStrategy && isNaN(recentlyAddedStrategy.minViews)) {
78 return 'Min views in recently added strategy is not a number' 85 return 'Min views in recently added strategy is not a number'
79 } 86 }
87 } else {
88 return 'Videos redundancy should be an array (you must uncomment lines containing - too)'
80 } 89 }
81 90
82 // Check storage directory locations 91 // Check storage directory locations
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts
index 7905d9ffa..622ad7d6b 100644
--- a/server/initializers/checker-before-init.ts
+++ b/server/initializers/checker-before-init.ts
@@ -1,6 +1,5 @@
1import * as config from 'config' 1import * as config from 'config'
2import { promisify0 } from '../helpers/core-utils' 2import { promisify0 } from '../helpers/core-utils'
3import { isArray } from '../helpers/custom-validators/misc'
4 3
5// ONLY USE CORE MODULES IN THIS FILE! 4// ONLY USE CORE MODULES IN THIS FILE!
6 5
@@ -12,19 +11,24 @@ function checkMissedConfig () {
12 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', 'database.pool.max', 11 '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', 12 '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', 13 'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
15 'storage.redundancy', 'storage.tmp', 14 'storage.redundancy', 'storage.tmp', 'storage.streaming_playlists',
16 'log.level', 15 'log.level',
17 'user.video_quota', 'user.video_quota_daily', 16 'user.video_quota', 'user.video_quota_daily',
17 'csp.enabled', 'csp.report_only', 'csp.report_uri',
18 'cache.previews.size', 'admin.email', 'contact_form.enabled', 18 'cache.previews.size', 'admin.email', 'contact_form.enabled',
19 'signup.enabled', 'signup.limit', 'signup.requires_email_verification', 19 'signup.enabled', 'signup.limit', 'signup.requires_email_verification',
20 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', 20 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
21 'redundancy.videos.strategies', 'redundancy.videos.check_interval', 21 'redundancy.videos.strategies', 'redundancy.videos.check_interval',
22 'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions', 22 'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions',
23 'import.videos.http.enabled', 'import.videos.torrent.enabled', 23 'import.videos.http.enabled', 'import.videos.torrent.enabled', 'auto_blacklist.videos.of_users.enabled',
24 'trending.videos.interval_days', 24 'trending.videos.interval_days',
25 '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',
26 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt', 26 'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt',
27 'services.twitter.username', 'services.twitter.whitelisted' 27 'services.twitter.username', 'services.twitter.whitelisted',
28 'followers.instance.enabled', 'followers.instance.manual_approval',
29 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces',
30 'history.videos.max_age', 'views.videos.remote.max_age',
31 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max'
28 ] 32 ]
29 const requiredAlternatives = [ 33 const requiredAlternatives = [
30 [ // set 34 [ // set
@@ -41,7 +45,8 @@ function checkMissedConfig () {
41 } 45 }
42 46
43 const redundancyVideos = config.get<any>('redundancy.videos.strategies') 47 const redundancyVideos = config.get<any>('redundancy.videos.strategies')
44 if (isArray(redundancyVideos)) { 48
49 if (Array.isArray(redundancyVideos)) {
45 for (const r of redundancyVideos) { 50 for (const r of redundancyVideos) {
46 if (!r.size) miss.push('redundancy.videos.strategies.size') 51 if (!r.size) miss.push('redundancy.videos.strategies.size')
47 if (!r.min_lifetime) miss.push('redundancy.videos.strategies.min_lifetime') 52 if (!r.min_lifetime) miss.push('redundancy.videos.strategies.min_lifetime')
diff --git a/server/initializers/config.ts b/server/initializers/config.ts
new file mode 100644
index 000000000..4f77e144d
--- /dev/null
+++ b/server/initializers/config.ts
@@ -0,0 +1,277 @@
1import { IConfig } from 'config'
2import { dirname, join } from 'path'
3import { VideosRedundancy } from '../../shared/models'
4// Do not use barrels, remain constants as independent as possible
5import { buildPath, parseBytes, parseDurationToMs, root } from '../helpers/core-utils'
6import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
7import * as bytes from 'bytes'
8
9// Use a variable to reload the configuration if we need
10let config: IConfig = require('config')
11
12const configChangedHandlers: Function[] = []
13
14const CONFIG = {
15 CUSTOM_FILE: getLocalConfigFilePath(),
16 LISTEN: {
17 PORT: config.get<number>('listen.port'),
18 HOSTNAME: config.get<string>('listen.hostname')
19 },
20 DATABASE: {
21 DBNAME: 'peertube' + config.get<string>('database.suffix'),
22 HOSTNAME: config.get<string>('database.hostname'),
23 PORT: config.get<number>('database.port'),
24 USERNAME: config.get<string>('database.username'),
25 PASSWORD: config.get<string>('database.password'),
26 POOL: {
27 MAX: config.get<number>('database.pool.max')
28 }
29 },
30 REDIS: {
31 HOSTNAME: config.has('redis.hostname') ? config.get<string>('redis.hostname') : null,
32 PORT: config.has('redis.port') ? config.get<number>('redis.port') : null,
33 SOCKET: config.has('redis.socket') ? config.get<string>('redis.socket') : null,
34 AUTH: config.has('redis.auth') ? config.get<string>('redis.auth') : null,
35 DB: config.has('redis.db') ? config.get<number>('redis.db') : null
36 },
37 SMTP: {
38 HOSTNAME: config.get<string>('smtp.hostname'),
39 PORT: config.get<number>('smtp.port'),
40 USERNAME: config.get<string>('smtp.username'),
41 PASSWORD: config.get<string>('smtp.password'),
42 TLS: config.get<boolean>('smtp.tls'),
43 DISABLE_STARTTLS: config.get<boolean>('smtp.disable_starttls'),
44 CA_FILE: config.get<string>('smtp.ca_file'),
45 FROM_ADDRESS: config.get<string>('smtp.from_address')
46 },
47 STORAGE: {
48 TMP_DIR: buildPath(config.get<string>('storage.tmp')),
49 AVATARS_DIR: buildPath(config.get<string>('storage.avatars')),
50 LOG_DIR: buildPath(config.get<string>('storage.logs')),
51 VIDEOS_DIR: buildPath(config.get<string>('storage.videos')),
52 STREAMING_PLAYLISTS_DIR: buildPath(config.get<string>('storage.streaming_playlists')),
53 REDUNDANCY_DIR: buildPath(config.get<string>('storage.redundancy')),
54 THUMBNAILS_DIR: buildPath(config.get<string>('storage.thumbnails')),
55 PREVIEWS_DIR: buildPath(config.get<string>('storage.previews')),
56 CAPTIONS_DIR: buildPath(config.get<string>('storage.captions')),
57 TORRENTS_DIR: buildPath(config.get<string>('storage.torrents')),
58 CACHE_DIR: buildPath(config.get<string>('storage.cache'))
59 },
60 WEBSERVER: {
61 SCHEME: config.get<boolean>('webserver.https') === true ? 'https' : 'http',
62 WS: config.get<boolean>('webserver.https') === true ? 'wss' : 'ws',
63 HOSTNAME: config.get<string>('webserver.hostname'),
64 PORT: config.get<number>('webserver.port')
65 },
66 RATES_LIMIT: {
67 LOGIN: {
68 WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.login.window')),
69 MAX: config.get<number>('rates_limit.login.max')
70 },
71 ASK_SEND_EMAIL: {
72 WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')),
73 MAX: config.get<number>('rates_limit.ask_send_email.max')
74 }
75 },
76 TRUST_PROXY: config.get<string[]>('trust_proxy'),
77 LOG: {
78 LEVEL: config.get<string>('log.level')
79 },
80 SEARCH: {
81 REMOTE_URI: {
82 USERS: config.get<boolean>('search.remote_uri.users'),
83 ANONYMOUS: config.get<boolean>('search.remote_uri.anonymous')
84 }
85 },
86 TRENDING: {
87 VIDEOS: {
88 INTERVAL_DAYS: config.get<number>('trending.videos.interval_days')
89 }
90 },
91 REDUNDANCY: {
92 VIDEOS: {
93 CHECK_INTERVAL: parseDurationToMs(config.get<string>('redundancy.videos.check_interval')),
94 STRATEGIES: buildVideosRedundancy(config.get<any[]>('redundancy.videos.strategies'))
95 }
96 },
97 CSP: {
98 ENABLED: config.get<boolean>('csp.enabled'),
99 REPORT_ONLY: config.get<boolean>('csp.report_only'),
100 REPORT_URI: config.get<boolean>('csp.report_uri')
101 },
102 TRACKER: {
103 ENABLED: config.get<boolean>('tracker.enabled'),
104 PRIVATE: config.get<boolean>('tracker.private'),
105 REJECT_TOO_MANY_ANNOUNCES: config.get<boolean>('tracker.reject_too_many_announces')
106 },
107 HISTORY: {
108 VIDEOS: {
109 MAX_AGE: parseDurationToMs(config.get('history.videos.max_age'))
110 }
111 },
112 VIEWS: {
113 VIDEOS: {
114 REMOTE: {
115 MAX_AGE: parseDurationToMs(config.get('views.videos.remote.max_age'))
116 }
117 }
118 },
119 ADMIN: {
120 get EMAIL () { return config.get<string>('admin.email') }
121 },
122 CONTACT_FORM: {
123 get ENABLED () { return config.get<boolean>('contact_form.enabled') }
124 },
125 SIGNUP: {
126 get ENABLED () { return config.get<boolean>('signup.enabled') },
127 get LIMIT () { return config.get<number>('signup.limit') },
128 get REQUIRES_EMAIL_VERIFICATION () { return config.get<boolean>('signup.requires_email_verification') },
129 FILTERS: {
130 CIDR: {
131 get WHITELIST () { return config.get<string[]>('signup.filters.cidr.whitelist') },
132 get BLACKLIST () { return config.get<string[]>('signup.filters.cidr.blacklist') }
133 }
134 }
135 },
136 USER: {
137 get VIDEO_QUOTA () { return parseBytes(config.get<number>('user.video_quota')) },
138 get VIDEO_QUOTA_DAILY () { return parseBytes(config.get<number>('user.video_quota_daily')) }
139 },
140 TRANSCODING: {
141 get ENABLED () { return config.get<boolean>('transcoding.enabled') },
142 get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
143 get THREADS () { return config.get<number>('transcoding.threads') },
144 RESOLUTIONS: {
145 get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
146 get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') },
147 get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') },
148 get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') },
149 get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') }
150 },
151 HLS: {
152 get ENABLED () { return config.get<boolean>('transcoding.hls.enabled') }
153 }
154 },
155 IMPORT: {
156 VIDEOS: {
157 HTTP: {
158 get ENABLED () { return config.get<boolean>('import.videos.http.enabled') }
159 },
160 TORRENT: {
161 get ENABLED () { return config.get<boolean>('import.videos.torrent.enabled') }
162 }
163 }
164 },
165 AUTO_BLACKLIST: {
166 VIDEOS: {
167 OF_USERS: {
168 get ENABLED () { return config.get<boolean>('auto_blacklist.videos.of_users.enabled') }
169 }
170 }
171 },
172 CACHE: {
173 PREVIEWS: {
174 get SIZE () { return config.get<number>('cache.previews.size') }
175 },
176 VIDEO_CAPTIONS: {
177 get SIZE () { return config.get<number>('cache.captions.size') }
178 }
179 },
180 INSTANCE: {
181 get NAME () { return config.get<string>('instance.name') },
182 get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') },
183 get DESCRIPTION () { return config.get<string>('instance.description') },
184 get TERMS () { return config.get<string>('instance.terms') },
185 get IS_NSFW () { return config.get<boolean>('instance.is_nsfw') },
186 get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') },
187 get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') },
188 CUSTOMIZATIONS: {
189 get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
190 get CSS () { return config.get<string>('instance.customizations.css') }
191 },
192 get ROBOTS () { return config.get<string>('instance.robots') },
193 get SECURITYTXT () { return config.get<string>('instance.securitytxt') },
194 get SECURITYTXT_CONTACT () { return config.get<string>('admin.email') }
195 },
196 SERVICES: {
197 TWITTER: {
198 get USERNAME () { return config.get<string>('services.twitter.username') },
199 get WHITELISTED () { return config.get<boolean>('services.twitter.whitelisted') }
200 }
201 },
202 FOLLOWERS: {
203 INSTANCE: {
204 get ENABLED () { return config.get<boolean>('followers.instance.enabled') },
205 get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') }
206 }
207 }
208}
209
210function registerConfigChangedHandler (fun: Function) {
211 configChangedHandlers.push(fun)
212}
213
214// ---------------------------------------------------------------------------
215
216export {
217 CONFIG,
218 registerConfigChangedHandler
219}
220
221// ---------------------------------------------------------------------------
222
223function getLocalConfigFilePath () {
224 const configSources = config.util.getConfigSources()
225 if (configSources.length === 0) throw new Error('Invalid config source.')
226
227 let filename = 'local'
228 if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
229 if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
230
231 return join(dirname(configSources[ 0 ].name), filename + '.json')
232}
233
234function buildVideosRedundancy (objs: any[]): VideosRedundancy[] {
235 if (!objs) return []
236
237 if (!Array.isArray(objs)) return objs
238
239 return objs.map(obj => {
240 return Object.assign({}, obj, {
241 minLifetime: parseDurationToMs(obj.min_lifetime),
242 size: bytes.parse(obj.size),
243 minViews: obj.min_views
244 })
245 })
246}
247
248export function reloadConfig () {
249
250 function directory () {
251 if (process.env.NODE_CONFIG_DIR) {
252 return process.env.NODE_CONFIG_DIR
253 }
254
255 return join(root(), 'config')
256 }
257
258 function purge () {
259 for (const fileName in require.cache) {
260 if (-1 === fileName.indexOf(directory())) {
261 continue
262 }
263
264 delete require.cache[fileName]
265 }
266
267 delete require.cache[require.resolve('config')]
268 }
269
270 purge()
271
272 config = require('config')
273
274 for (const configChangedHandler of configChangedHandlers) {
275 configChangedHandler()
276 }
277}
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 6f3ebb9aa..62778ae58 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -1,22 +1,20 @@
1import { IConfig } from 'config' 1import { join } from 'path'
2import { dirname, join } from 'path' 2import { JobType, VideoRateType, VideoState } from '../../shared/models'
3import { JobType, VideoRateType, VideoState, VideosRedundancy } from '../../shared/models'
4import { ActivityPubActorType } from '../../shared/models/activitypub' 3import { ActivityPubActorType } from '../../shared/models/activitypub'
5import { FollowState } from '../../shared/models/actors' 4import { FollowState } from '../../shared/models/actors'
6import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos' 5import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos'
7// Do not use barrels, remain constants as independent as possible 6// Do not use barrels, remain constants as independent as possible
8import { buildPath, isTestInstance, parseDuration, parseBytes, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' 7import { isTestInstance, sanitizeHost, sanitizeUrl } from '../helpers/core-utils'
9import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' 8import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
10import { invert } from 'lodash' 9import { invert } from 'lodash'
11import { CronRepeatOptions, EveryRepeatOptions } from 'bull' 10import { CronRepeatOptions, EveryRepeatOptions } from 'bull'
12import * as bytes from 'bytes' 11import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model'
13 12import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model'
14// Use a variable to reload the configuration if we need 13import { CONFIG, registerConfigChangedHandler } from './config'
15let config: IConfig = require('config')
16 14
17// --------------------------------------------------------------------------- 15// ---------------------------------------------------------------------------
18 16
19const LAST_MIGRATION_VERSION = 325 17const LAST_MIGRATION_VERSION = 375
20 18
21// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
22 20
@@ -30,6 +28,15 @@ const PAGINATION = {
30 } 28 }
31} 29}
32 30
31const WEBSERVER = {
32 URL: '',
33 HOST: '',
34 SCHEME: '',
35 WS: '',
36 HOSTNAME: '',
37 PORT: 0
38}
39
33// Sortable columns per schema 40// Sortable columns per schema
34const SORTABLE_COLUMNS = { 41const SORTABLE_COLUMNS = {
35 USERS: [ 'id', 'username', 'createdAt' ], 42 USERS: [ 'id', 'username', 'createdAt' ],
@@ -40,6 +47,7 @@ const SORTABLE_COLUMNS = {
40 VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], 47 VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
41 VIDEO_IMPORTS: [ 'createdAt' ], 48 VIDEO_IMPORTS: [ 'createdAt' ],
42 VIDEO_COMMENT_THREADS: [ 'createdAt' ], 49 VIDEO_COMMENT_THREADS: [ 'createdAt' ],
50 VIDEO_RATES: [ 'createdAt' ],
43 BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ], 51 BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ],
44 FOLLOWERS: [ 'createdAt' ], 52 FOLLOWERS: [ 'createdAt' ],
45 FOLLOWING: [ 'createdAt' ], 53 FOLLOWING: [ 'createdAt' ],
@@ -52,7 +60,9 @@ const SORTABLE_COLUMNS = {
52 ACCOUNTS_BLOCKLIST: [ 'createdAt' ], 60 ACCOUNTS_BLOCKLIST: [ 'createdAt' ],
53 SERVERS_BLOCKLIST: [ 'createdAt' ], 61 SERVERS_BLOCKLIST: [ 'createdAt' ],
54 62
55 USER_NOTIFICATIONS: [ 'createdAt' ] 63 USER_NOTIFICATIONS: [ 'createdAt' ],
64
65 VIDEO_PLAYLISTS: [ 'displayName', 'createdAt', 'updatedAt' ]
56} 66}
57 67
58const OAUTH_LIFETIME = { 68const OAUTH_LIFETIME = {
@@ -96,36 +106,40 @@ const REMOTE_SCHEME = {
96 WS: 'wss' 106 WS: 'wss'
97} 107}
98 108
99const JOB_ATTEMPTS: { [ id in JobType ]: number } = { 109// TODO: remove 'video-file'
110const JOB_ATTEMPTS: { [id in (JobType | 'video-file')]: number } = {
100 'activitypub-http-broadcast': 5, 111 'activitypub-http-broadcast': 5,
101 'activitypub-http-unicast': 5, 112 'activitypub-http-unicast': 5,
102 'activitypub-http-fetcher': 5, 113 'activitypub-http-fetcher': 5,
103 'activitypub-follow': 5, 114 'activitypub-follow': 5,
104 'video-file-import': 1, 115 'video-file-import': 1,
116 'video-transcoding': 1,
105 'video-file': 1, 117 'video-file': 1,
106 'video-import': 1, 118 'video-import': 1,
107 'email': 5, 119 'email': 5,
108 'videos-views': 1, 120 'videos-views': 1,
109 'activitypub-refresher': 1 121 'activitypub-refresher': 1
110} 122}
111const JOB_CONCURRENCY: { [ id in JobType ]: number } = { 123const JOB_CONCURRENCY: { [id in (JobType | 'video-file')]: number } = {
112 'activitypub-http-broadcast': 1, 124 'activitypub-http-broadcast': 1,
113 'activitypub-http-unicast': 5, 125 'activitypub-http-unicast': 5,
114 'activitypub-http-fetcher': 1, 126 'activitypub-http-fetcher': 1,
115 'activitypub-follow': 3, 127 'activitypub-follow': 3,
116 'video-file-import': 1, 128 'video-file-import': 1,
129 'video-transcoding': 1,
117 'video-file': 1, 130 'video-file': 1,
118 'video-import': 1, 131 'video-import': 1,
119 'email': 5, 132 'email': 5,
120 'videos-views': 1, 133 'videos-views': 1,
121 'activitypub-refresher': 1 134 'activitypub-refresher': 1
122} 135}
123const JOB_TTL: { [ id in JobType ]: number } = { 136const JOB_TTL: { [id in (JobType | 'video-file')]: number } = {
124 'activitypub-http-broadcast': 60000 * 10, // 10 minutes 137 'activitypub-http-broadcast': 60000 * 10, // 10 minutes
125 'activitypub-http-unicast': 60000 * 10, // 10 minutes 138 'activitypub-http-unicast': 60000 * 10, // 10 minutes
126 'activitypub-http-fetcher': 60000 * 10, // 10 minutes 139 'activitypub-http-fetcher': 60000 * 10, // 10 minutes
127 'activitypub-follow': 60000 * 10, // 10 minutes 140 'activitypub-follow': 60000 * 10, // 10 minutes
128 'video-file-import': 1000 * 3600, // 1 hour 141 'video-file-import': 1000 * 3600, // 1 hour
142 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long
129 'video-file': 1000 * 3600 * 48, // 2 days, transcoding could be long 143 'video-file': 1000 * 3600 * 48, // 2 days, transcoding could be long
130 'video-import': 1000 * 3600 * 2, // hours 144 'video-import': 1000 * 3600 * 2, // hours
131 'email': 60000 * 10, // 10 minutes 145 'email': 60000 * 10, // 10 minutes
@@ -144,163 +158,13 @@ const JOB_REQUEST_TIMEOUT = 3000 // 3 seconds
144const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2 // 2 days 158const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2 // 2 days
145const VIDEO_IMPORT_TIMEOUT = 1000 * 3600 // 1 hour 159const VIDEO_IMPORT_TIMEOUT = 1000 * 3600 // 1 hour
146 160
147// 1 hour 161const SCHEDULER_INTERVALS_MS = {
148let SCHEDULER_INTERVALS_MS = {
149 actorFollowScores: 60000 * 60, // 1 hour 162 actorFollowScores: 60000 * 60, // 1 hour
150 removeOldJobs: 60000 * 60, // 1 hour 163 removeOldJobs: 60000 * 60, // 1 hour
151 updateVideos: 60000, // 1 minute 164 updateVideos: 60000, // 1 minute
152 youtubeDLUpdate: 60000 * 60 * 24 // 1 day 165 youtubeDLUpdate: 60000 * 60 * 24, // 1 day
153} 166 removeOldViews: 60000 * 60 * 24, // 1 day
154 167 removeOldHistory: 60000 * 60 * 24 // 1 day
155// ---------------------------------------------------------------------------
156
157const CONFIG = {
158 CUSTOM_FILE: getLocalConfigFilePath(),
159 LISTEN: {
160 PORT: config.get<number>('listen.port'),
161 HOSTNAME: config.get<string>('listen.hostname')
162 },
163 DATABASE: {
164 DBNAME: 'peertube' + config.get<string>('database.suffix'),
165 HOSTNAME: config.get<string>('database.hostname'),
166 PORT: config.get<number>('database.port'),
167 USERNAME: config.get<string>('database.username'),
168 PASSWORD: config.get<string>('database.password'),
169 POOL: {
170 MAX: config.get<number>('database.pool.max')
171 }
172 },
173 REDIS: {
174 HOSTNAME: config.has('redis.hostname') ? config.get<string>('redis.hostname') : null,
175 PORT: config.has('redis.port') ? config.get<number>('redis.port') : null,
176 SOCKET: config.has('redis.socket') ? config.get<string>('redis.socket') : null,
177 AUTH: config.has('redis.auth') ? config.get<string>('redis.auth') : null,
178 DB: config.has('redis.db') ? config.get<number>('redis.db') : null
179 },
180 SMTP: {
181 HOSTNAME: config.get<string>('smtp.hostname'),
182 PORT: config.get<number>('smtp.port'),
183 USERNAME: config.get<string>('smtp.username'),
184 PASSWORD: config.get<string>('smtp.password'),
185 TLS: config.get<boolean>('smtp.tls'),
186 DISABLE_STARTTLS: config.get<boolean>('smtp.disable_starttls'),
187 CA_FILE: config.get<string>('smtp.ca_file'),
188 FROM_ADDRESS: config.get<string>('smtp.from_address')
189 },
190 STORAGE: {
191 TMP_DIR: buildPath(config.get<string>('storage.tmp')),
192 AVATARS_DIR: buildPath(config.get<string>('storage.avatars')),
193 LOG_DIR: buildPath(config.get<string>('storage.logs')),
194 VIDEOS_DIR: buildPath(config.get<string>('storage.videos')),
195 REDUNDANCY_DIR: buildPath(config.get<string>('storage.redundancy')),
196 THUMBNAILS_DIR: buildPath(config.get<string>('storage.thumbnails')),
197 PREVIEWS_DIR: buildPath(config.get<string>('storage.previews')),
198 CAPTIONS_DIR: buildPath(config.get<string>('storage.captions')),
199 TORRENTS_DIR: buildPath(config.get<string>('storage.torrents')),
200 CACHE_DIR: buildPath(config.get<string>('storage.cache'))
201 },
202 WEBSERVER: {
203 SCHEME: config.get<boolean>('webserver.https') === true ? 'https' : 'http',
204 WS: config.get<boolean>('webserver.https') === true ? 'wss' : 'ws',
205 HOSTNAME: config.get<string>('webserver.hostname'),
206 PORT: config.get<number>('webserver.port'),
207 URL: '',
208 HOST: ''
209 },
210 TRUST_PROXY: config.get<string[]>('trust_proxy'),
211 LOG: {
212 LEVEL: config.get<string>('log.level')
213 },
214 SEARCH: {
215 REMOTE_URI: {
216 USERS: config.get<boolean>('search.remote_uri.users'),
217 ANONYMOUS: config.get<boolean>('search.remote_uri.anonymous')
218 }
219 },
220 TRENDING: {
221 VIDEOS: {
222 INTERVAL_DAYS: config.get<number>('trending.videos.interval_days')
223 }
224 },
225 REDUNDANCY: {
226 VIDEOS: {
227 CHECK_INTERVAL: parseDuration(config.get<string>('redundancy.videos.check_interval')),
228 STRATEGIES: buildVideosRedundancy(config.get<any[]>('redundancy.videos.strategies'))
229 }
230 },
231 ADMIN: {
232 get EMAIL () { return config.get<string>('admin.email') }
233 },
234 CONTACT_FORM: {
235 get ENABLED () { return config.get<boolean>('contact_form.enabled') }
236 },
237 SIGNUP: {
238 get ENABLED () { return config.get<boolean>('signup.enabled') },
239 get LIMIT () { return config.get<number>('signup.limit') },
240 get REQUIRES_EMAIL_VERIFICATION () { return config.get<boolean>('signup.requires_email_verification') },
241 FILTERS: {
242 CIDR: {
243 get WHITELIST () { return config.get<string[]>('signup.filters.cidr.whitelist') },
244 get BLACKLIST () { return config.get<string[]>('signup.filters.cidr.blacklist') }
245 }
246 }
247 },
248 USER: {
249 get VIDEO_QUOTA () { return parseBytes(config.get<number>('user.video_quota')) },
250 get VIDEO_QUOTA_DAILY () { return parseBytes(config.get<number>('user.video_quota_daily')) }
251 },
252 TRANSCODING: {
253 get ENABLED () { return config.get<boolean>('transcoding.enabled') },
254 get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
255 get THREADS () { return config.get<number>('transcoding.threads') },
256 RESOLUTIONS: {
257 get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
258 get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') },
259 get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') },
260 get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') },
261 get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') }
262 }
263 },
264 IMPORT: {
265 VIDEOS: {
266 HTTP: {
267 get ENABLED () { return config.get<boolean>('import.videos.http.enabled') }
268 },
269 TORRENT: {
270 get ENABLED () { return config.get<boolean>('import.videos.torrent.enabled') }
271 }
272 }
273 },
274 CACHE: {
275 PREVIEWS: {
276 get SIZE () { return config.get<number>('cache.previews.size') }
277 },
278 VIDEO_CAPTIONS: {
279 get SIZE () { return config.get<number>('cache.captions.size') }
280 }
281 },
282 INSTANCE: {
283 get NAME () { return config.get<string>('instance.name') },
284 get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') },
285 get DESCRIPTION () { return config.get<string>('instance.description') },
286 get TERMS () { return config.get<string>('instance.terms') },
287 get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') },
288 get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') },
289 CUSTOMIZATIONS: {
290 get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
291 get CSS () { return config.get<string>('instance.customizations.css') }
292 },
293 get ROBOTS () { return config.get<string>('instance.robots') },
294 get SECURITYTXT () { return config.get<string>('instance.securitytxt') },
295 get SECURITYTXT_CONTACT () { return config.get<string>('admin.email') }
296 },
297 SERVICES: {
298 get 'CSP-LOGGER' () { return config.get<string>('services.csp-logger') },
299 TWITTER: {
300 get USERNAME () { return config.get<string>('services.twitter.username') },
301 get WHITELISTED () { return config.get<boolean>('services.twitter.whitelisted') }
302 }
303 }
304} 168}
305 169
306// --------------------------------------------------------------------------- 170// ---------------------------------------------------------------------------
@@ -377,6 +241,17 @@ let CONSTRAINTS_FIELDS = {
377 FILE_SIZE: { min: 10 }, 241 FILE_SIZE: { min: 10 },
378 URL: { min: 3, max: 2000 } // Length 242 URL: { min: 3, max: 2000 } // Length
379 }, 243 },
244 VIDEO_PLAYLISTS: {
245 NAME: { min: 1, max: 120 }, // Length
246 DESCRIPTION: { min: 3, max: 1000 }, // Length
247 URL: { min: 3, max: 2000 }, // Length
248 IMAGE: {
249 EXTNAME: [ '.jpg', '.jpeg' ],
250 FILE_SIZE: {
251 max: 2 * 1024 * 1024 // 2MB
252 }
253 }
254 },
380 ACTORS: { 255 ACTORS: {
381 PUBLIC_KEY: { min: 10, max: 5000 }, // Length 256 PUBLIC_KEY: { min: 10, max: 5000 }, // Length
382 PRIVATE_KEY: { min: 10, max: 5000 }, // Length 257 PRIVATE_KEY: { min: 10, max: 5000 }, // Length
@@ -406,12 +281,12 @@ let CONSTRAINTS_FIELDS = {
406 281
407const RATES_LIMIT = { 282const RATES_LIMIT = {
408 LOGIN: { 283 LOGIN: {
409 WINDOW_MS: 5 * 60 * 1000, // 5 minutes 284 WINDOW_MS: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS,
410 MAX: 15 // 15 attempts 285 MAX: CONFIG.RATES_LIMIT.LOGIN.MAX
411 }, 286 },
412 ASK_SEND_EMAIL: { 287 ASK_SEND_EMAIL: {
413 WINDOW_MS: 5 * 60 * 1000, // 5 minutes 288 WINDOW_MS: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS,
414 MAX: 3 // 3 attempts 289 MAX: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX
415 } 290 }
416} 291}
417 292
@@ -467,30 +342,41 @@ const VIDEO_LICENCES = {
467 7: 'Public Domain Dedication' 342 7: 'Public Domain Dedication'
468} 343}
469 344
470const VIDEO_LANGUAGES = buildLanguages() 345let VIDEO_LANGUAGES: { [id: string]: string } = {}
471 346
472const VIDEO_PRIVACIES = { 347const VIDEO_PRIVACIES = {
473 [VideoPrivacy.PUBLIC]: 'Public', 348 [ VideoPrivacy.PUBLIC ]: 'Public',
474 [VideoPrivacy.UNLISTED]: 'Unlisted', 349 [ VideoPrivacy.UNLISTED ]: 'Unlisted',
475 [VideoPrivacy.PRIVATE]: 'Private' 350 [ VideoPrivacy.PRIVATE ]: 'Private'
476} 351}
477 352
478const VIDEO_STATES = { 353const VIDEO_STATES = {
479 [VideoState.PUBLISHED]: 'Published', 354 [ VideoState.PUBLISHED ]: 'Published',
480 [VideoState.TO_TRANSCODE]: 'To transcode', 355 [ VideoState.TO_TRANSCODE ]: 'To transcode',
481 [VideoState.TO_IMPORT]: 'To import' 356 [ VideoState.TO_IMPORT ]: 'To import'
482} 357}
483 358
484const VIDEO_IMPORT_STATES = { 359const VIDEO_IMPORT_STATES = {
485 [VideoImportState.FAILED]: 'Failed', 360 [ VideoImportState.FAILED ]: 'Failed',
486 [VideoImportState.PENDING]: 'Pending', 361 [ VideoImportState.PENDING ]: 'Pending',
487 [VideoImportState.SUCCESS]: 'Success' 362 [ VideoImportState.SUCCESS ]: 'Success'
488} 363}
489 364
490const VIDEO_ABUSE_STATES = { 365const VIDEO_ABUSE_STATES = {
491 [VideoAbuseState.PENDING]: 'Pending', 366 [ VideoAbuseState.PENDING ]: 'Pending',
492 [VideoAbuseState.REJECTED]: 'Rejected', 367 [ VideoAbuseState.REJECTED ]: 'Rejected',
493 [VideoAbuseState.ACCEPTED]: 'Accepted' 368 [ VideoAbuseState.ACCEPTED ]: 'Accepted'
369}
370
371const VIDEO_PLAYLIST_PRIVACIES = {
372 [ VideoPlaylistPrivacy.PUBLIC ]: 'Public',
373 [ VideoPlaylistPrivacy.UNLISTED ]: 'Unlisted',
374 [ VideoPlaylistPrivacy.PRIVATE ]: 'Private'
375}
376
377const VIDEO_PLAYLIST_TYPES = {
378 [ VideoPlaylistType.REGULAR ]: 'Regular',
379 [ VideoPlaylistType.WATCH_LATER ]: 'Watch later'
494} 380}
495 381
496const MIMETYPES = { 382const MIMETYPES = {
@@ -548,8 +434,9 @@ const ACTIVITY_PUB = {
548 MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ] 434 MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
549 }, 435 },
550 MAX_RECURSION_COMMENTS: 100, 436 MAX_RECURSION_COMMENTS: 100,
551 ACTOR_REFRESH_INTERVAL: 3600 * 24 * 1000, // 1 day 437 ACTOR_REFRESH_INTERVAL: 3600 * 24 * 1000 * 2, // 2 days
552 VIDEO_REFRESH_INTERVAL: 3600 * 24 * 1000 // 1 day 438 VIDEO_REFRESH_INTERVAL: 3600 * 24 * 1000 * 2, // 2 days
439 VIDEO_PLAYLIST_REFRESH_INTERVAL: 3600 * 24 * 1000 * 2 // 2 days
553} 440}
554 441
555const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = { 442const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
@@ -575,7 +462,7 @@ const USER_PASSWORD_RESET_LIFETIME = 60000 * 5 // 5 minutes
575 462
576const USER_EMAIL_VERIFY_LIFETIME = 60000 * 60 // 60 minutes 463const USER_EMAIL_VERIFY_LIFETIME = 60000 * 60 // 60 minutes
577 464
578const NSFW_POLICY_TYPES: { [ id: string]: NSFWPolicyType } = { 465const NSFW_POLICY_TYPES: { [ id: string ]: NSFWPolicyType } = {
579 DO_NOT_LIST: 'do_not_list', 466 DO_NOT_LIST: 'do_not_list',
580 BLUR: 'blur', 467 BLUR: 'blur',
581 DISPLAY: 'display' 468 DISPLAY: 'display'
@@ -590,6 +477,9 @@ const STATIC_PATHS = {
590 TORRENTS: '/static/torrents/', 477 TORRENTS: '/static/torrents/',
591 WEBSEED: '/static/webseed/', 478 WEBSEED: '/static/webseed/',
592 REDUNDANCY: '/static/redundancy/', 479 REDUNDANCY: '/static/redundancy/',
480 STREAMING_PLAYLISTS: {
481 HLS: '/static/streaming-playlists/hls'
482 },
593 AVATARS: '/static/avatars/', 483 AVATARS: '/static/avatars/',
594 VIDEO_CAPTIONS: '/static/video-captions/' 484 VIDEO_CAPTIONS: '/static/video-captions/'
595} 485}
@@ -603,8 +493,8 @@ let STATIC_MAX_AGE = '2h'
603 493
604// Videos thumbnail size 494// Videos thumbnail size
605const THUMBNAILS_SIZE = { 495const THUMBNAILS_SIZE = {
606 width: 200, 496 width: 223,
607 height: 110 497 height: 122
608} 498}
609const PREVIEWS_SIZE = { 499const PREVIEWS_SIZE = {
610 width: 560, 500 width: 560,
@@ -621,7 +511,7 @@ const EMBED_SIZE = {
621} 511}
622 512
623// Sub folders of cache directory 513// Sub folders of cache directory
624const CACHE = { 514const FILES_CACHE = {
625 PREVIEWS: { 515 PREVIEWS: {
626 DIRECTORY: join(CONFIG.STORAGE.CACHE_DIR, 'previews'), 516 DIRECTORY: join(CONFIG.STORAGE.CACHE_DIR, 'previews'),
627 MAX_AGE: 1000 * 3600 * 3 // 3 hours 517 MAX_AGE: 1000 * 3600 * 3 // 3 hours
@@ -632,6 +522,15 @@ const CACHE = {
632 } 522 }
633} 523}
634 524
525const CACHE = {
526 USER_TOKENS: {
527 MAX_SIZE: 10000
528 }
529}
530
531const HLS_STREAMING_PLAYLIST_DIRECTORY = join(CONFIG.STORAGE.STREAMING_PLAYLISTS_DIR, 'hls')
532const HLS_REDUNDANCY_DIRECTORY = join(CONFIG.STORAGE.REDUNDANCY_DIR, 'hls')
533
635const MEMOIZE_TTL = { 534const MEMOIZE_TTL = {
636 OVERVIEWS_SAMPLE: 1000 * 3600 * 4 // 4 hours 535 OVERVIEWS_SAMPLE: 1000 * 3600 * 4 // 4 hours
637} 536}
@@ -650,7 +549,7 @@ const CUSTOM_HTML_TAG_COMMENTS = {
650 TITLE: '<!-- title tag -->', 549 TITLE: '<!-- title tag -->',
651 DESCRIPTION: '<!-- description tag -->', 550 DESCRIPTION: '<!-- description tag -->',
652 CUSTOM_CSS: '<!-- custom css tag -->', 551 CUSTOM_CSS: '<!-- custom css tag -->',
653 OPENGRAPH_AND_OEMBED: '<!-- open graph and oembed tags -->' 552 META_TAGS: '<!-- meta tags -->'
654} 553}
655 554
656// --------------------------------------------------------------------------- 555// ---------------------------------------------------------------------------
@@ -659,6 +558,8 @@ const FEEDS = {
659 COUNT: 20 558 COUNT: 20
660} 559}
661 560
561const MAX_LOGS_OUTPUT_CHARACTERS = 10 * 1000 * 1000
562
662// --------------------------------------------------------------------------- 563// ---------------------------------------------------------------------------
663 564
664const TRACKER_RATE_LIMITS = { 565const TRACKER_RATE_LIMITS = {
@@ -667,6 +568,8 @@ const TRACKER_RATE_LIMITS = {
667 ANNOUNCES_PER_IP: 30 // maximum announces for all our torrents in the interval 568 ANNOUNCES_PER_IP: 30 // maximum announces for all our torrents in the interval
668} 569}
669 570
571const P2P_MEDIA_LOADER_PEER_VERSION = 2
572
670// --------------------------------------------------------------------------- 573// ---------------------------------------------------------------------------
671 574
672// Special constants for a test instance 575// Special constants for a test instance
@@ -683,38 +586,50 @@ if (isTestInstance() === true) {
683 ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE = 2 586 ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE = 2
684 ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds 587 ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds
685 ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL = 10 * 1000 // 10 seconds 588 ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL = 10 * 1000 // 10 seconds
589 ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL = 10 * 1000 // 10 seconds
686 590
687 CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB 591 CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB
688 592
689 SCHEDULER_INTERVALS_MS.actorFollowScores = 1000 593 SCHEDULER_INTERVALS_MS.actorFollowScores = 1000
690 SCHEDULER_INTERVALS_MS.removeOldJobs = 10000 594 SCHEDULER_INTERVALS_MS.removeOldJobs = 10000
595 SCHEDULER_INTERVALS_MS.removeOldHistory = 5000
596 SCHEDULER_INTERVALS_MS.removeOldViews = 5000
691 SCHEDULER_INTERVALS_MS.updateVideos = 5000 597 SCHEDULER_INTERVALS_MS.updateVideos = 5000
692 REPEAT_JOBS['videos-views'] = { every: 5000 } 598 REPEAT_JOBS[ 'videos-views' ] = { every: 5000 }
693 599
694 REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 600 REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1
695 601
696 VIDEO_VIEW_LIFETIME = 1000 // 1 second 602 VIDEO_VIEW_LIFETIME = 1000 // 1 second
697 CONTACT_FORM_LIFETIME = 1000 // 1 second 603 CONTACT_FORM_LIFETIME = 1000 // 1 second
698 604
699 JOB_ATTEMPTS['email'] = 1 605 JOB_ATTEMPTS[ 'email' ] = 1
700 606
701 CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000 607 FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000
702 MEMOIZE_TTL.OVERVIEWS_SAMPLE = 1 608 MEMOIZE_TTL.OVERVIEWS_SAMPLE = 1
703 ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms' 609 ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms'
610
611 RATES_LIMIT.LOGIN.MAX = 20
704} 612}
705 613
706updateWebserverUrls() 614updateWebserverUrls()
707 615
616registerConfigChangedHandler(() => {
617 updateWebserverUrls()
618 updateWebserverConfig()
619})
620
708// --------------------------------------------------------------------------- 621// ---------------------------------------------------------------------------
709 622
710export { 623export {
624 WEBSERVER,
711 API_VERSION, 625 API_VERSION,
626 HLS_REDUNDANCY_DIRECTORY,
627 P2P_MEDIA_LOADER_PEER_VERSION,
712 AVATARS_SIZE, 628 AVATARS_SIZE,
713 ACCEPT_HEADERS, 629 ACCEPT_HEADERS,
714 BCRYPT_SALT_SIZE, 630 BCRYPT_SALT_SIZE,
715 TRACKER_RATE_LIMITS, 631 TRACKER_RATE_LIMITS,
716 CACHE, 632 FILES_CACHE,
717 CONFIG,
718 CONSTRAINTS_FIELDS, 633 CONSTRAINTS_FIELDS,
719 EMBED_SIZE, 634 EMBED_SIZE,
720 REDUNDANCY, 635 REDUNDANCY,
@@ -733,12 +648,15 @@ export {
733 PRIVATE_RSA_KEY_SIZE, 648 PRIVATE_RSA_KEY_SIZE,
734 ROUTE_CACHE_LIFETIME, 649 ROUTE_CACHE_LIFETIME,
735 SORTABLE_COLUMNS, 650 SORTABLE_COLUMNS,
651 HLS_STREAMING_PLAYLIST_DIRECTORY,
736 FEEDS, 652 FEEDS,
737 JOB_TTL, 653 JOB_TTL,
738 NSFW_POLICY_TYPES, 654 NSFW_POLICY_TYPES,
739 STATIC_MAX_AGE, 655 STATIC_MAX_AGE,
740 STATIC_PATHS, 656 STATIC_PATHS,
741 VIDEO_IMPORT_TIMEOUT, 657 VIDEO_IMPORT_TIMEOUT,
658 VIDEO_PLAYLIST_TYPES,
659 MAX_LOGS_OUTPUT_CHARACTERS,
742 ACTIVITY_PUB, 660 ACTIVITY_PUB,
743 ACTIVITY_PUB_ACTOR_TYPES, 661 ACTIVITY_PUB_ACTOR_TYPES,
744 THUMBNAILS_SIZE, 662 THUMBNAILS_SIZE,
@@ -751,6 +669,7 @@ export {
751 VIDEO_TRANSCODING_FPS, 669 VIDEO_TRANSCODING_FPS,
752 FFMPEG_NICE, 670 FFMPEG_NICE,
753 VIDEO_ABUSE_STATES, 671 VIDEO_ABUSE_STATES,
672 CACHE,
754 JOB_REQUEST_TIMEOUT, 673 JOB_REQUEST_TIMEOUT,
755 USER_PASSWORD_RESET_LIFETIME, 674 USER_PASSWORD_RESET_LIFETIME,
756 MEMOIZE_TTL, 675 MEMOIZE_TTL,
@@ -767,22 +686,13 @@ export {
767 VIDEO_IMPORT_STATES, 686 VIDEO_IMPORT_STATES,
768 VIDEO_VIEW_LIFETIME, 687 VIDEO_VIEW_LIFETIME,
769 CONTACT_FORM_LIFETIME, 688 CONTACT_FORM_LIFETIME,
689 VIDEO_PLAYLIST_PRIVACIES,
690 loadLanguages,
770 buildLanguages 691 buildLanguages
771} 692}
772 693
773// --------------------------------------------------------------------------- 694// ---------------------------------------------------------------------------
774 695
775function getLocalConfigFilePath () {
776 const configSources = config.util.getConfigSources()
777 if (configSources.length === 0) throw new Error('Invalid config source.')
778
779 let filename = 'local'
780 if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
781 if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
782
783 return join(dirname(configSources[ 0 ].name), filename + '.json')
784}
785
786function buildVideoMimetypeExt () { 696function buildVideoMimetypeExt () {
787 const data = { 697 const data = {
788 'video/webm': '.webm', 698 'video/webm': '.webm',
@@ -805,8 +715,12 @@ function buildVideoMimetypeExt () {
805} 715}
806 716
807function updateWebserverUrls () { 717function updateWebserverUrls () {
808 CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT) 718 WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT)
809 CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP) 719 WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP)
720 WEBSERVER.SCHEME = CONFIG.WEBSERVER.SCHEME
721 WEBSERVER.WS = CONFIG.WEBSERVER.WS
722 WEBSERVER.HOSTNAME = CONFIG.WEBSERVER.HOSTNAME
723 WEBSERVER.PORT = CONFIG.WEBSERVER.PORT
810} 724}
811 725
812function updateWebserverConfig () { 726function updateWebserverConfig () {
@@ -822,16 +736,8 @@ function buildVideosExtname () {
822 : [ '.mp4', '.ogv', '.webm' ] 736 : [ '.mp4', '.ogv', '.webm' ]
823} 737}
824 738
825function buildVideosRedundancy (objs: any[]): VideosRedundancy[] { 739function loadLanguages () {
826 if (!objs) return [] 740 Object.assign(VIDEO_LANGUAGES, buildLanguages())
827
828 return objs.map(obj => {
829 return Object.assign({}, obj, {
830 minLifetime: parseDuration(obj.min_lifetime),
831 size: bytes.parse(obj.size),
832 minViews: obj.min_views
833 })
834 })
835} 741}
836 742
837function buildLanguages () { 743function buildLanguages () {
@@ -866,42 +772,13 @@ function buildLanguages () {
866 iso639 772 iso639
867 .filter(l => { 773 .filter(l => {
868 return (l.iso6391 !== null && l.type === 'living') || 774 return (l.iso6391 !== null && l.type === 'living') ||
869 additionalLanguages[l.iso6393] === true 775 additionalLanguages[ l.iso6393 ] === true
870 }) 776 })
871 .forEach(l => languages[l.iso6391 || l.iso6393] = l.name) 777 .forEach(l => languages[ l.iso6391 || l.iso6393 ] = l.name)
872 778
873 // Override Occitan label 779 // Override Occitan label
874 languages['oc'] = 'Occitan' 780 languages[ 'oc' ] = 'Occitan'
781 languages[ 'el' ] = 'Greek'
875 782
876 return languages 783 return languages
877} 784}
878
879export function reloadConfig () {
880
881 function directory () {
882 if (process.env.NODE_CONFIG_DIR) {
883 return process.env.NODE_CONFIG_DIR
884 }
885
886 return join(root(), 'config')
887 }
888
889 function purge () {
890 for (const fileName in require.cache) {
891 if (-1 === fileName.indexOf(directory())) {
892 continue
893 }
894
895 delete require.cache[fileName]
896 }
897
898 delete require.cache[require.resolve('config')]
899 }
900
901 purge()
902
903 config = require('config')
904
905 updateWebserverConfig()
906 updateWebserverUrls()
907}
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index 84ad2079b..142063a99 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -21,7 +21,7 @@ import { VideoCommentModel } from '../models/video/video-comment'
21import { VideoFileModel } from '../models/video/video-file' 21import { VideoFileModel } from '../models/video/video-file'
22import { VideoShareModel } from '../models/video/video-share' 22import { VideoShareModel } from '../models/video/video-share'
23import { VideoTagModel } from '../models/video/video-tag' 23import { VideoTagModel } from '../models/video/video-tag'
24import { CONFIG } from './constants' 24import { CONFIG } from './config'
25import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update' 25import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update'
26import { VideoCaptionModel } from '../models/video/video-caption' 26import { VideoCaptionModel } from '../models/video/video-caption'
27import { VideoImportModel } from '../models/video/video-import' 27import { VideoImportModel } from '../models/video/video-import'
@@ -33,6 +33,11 @@ import { AccountBlocklistModel } from '../models/account/account-blocklist'
33import { ServerBlocklistModel } from '../models/server/server-blocklist' 33import { ServerBlocklistModel } from '../models/server/server-blocklist'
34import { UserNotificationModel } from '../models/account/user-notification' 34import { UserNotificationModel } from '../models/account/user-notification'
35import { UserNotificationSettingModel } from '../models/account/user-notification-setting' 35import { UserNotificationSettingModel } from '../models/account/user-notification-setting'
36import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
37import { VideoPlaylistModel } from '../models/video/video-playlist'
38import { VideoPlaylistElementModel } from '../models/video/video-playlist-element'
39import { ThumbnailModel } from '../models/video/thumbnail'
40import { QueryTypes, Transaction } from 'sequelize'
36 41
37require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string 42require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string
38 43
@@ -54,8 +59,7 @@ const sequelizeTypescript = new SequelizeTypescript({
54 max: poolMax 59 max: poolMax
55 }, 60 },
56 benchmark: isTestInstance(), 61 benchmark: isTestInstance(),
57 isolationLevel: SequelizeTypescript.Transaction.ISOLATION_LEVELS.SERIALIZABLE, 62 isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE,
58 operatorsAliases: false,
59 logging: (message: string, benchmark: number) => { 63 logging: (message: string, benchmark: number) => {
60 if (process.env.NODE_DB_LOG === 'false') return 64 if (process.env.NODE_DB_LOG === 'false') return
61 65
@@ -82,6 +86,7 @@ async function initDatabaseModels (silent: boolean) {
82 AccountVideoRateModel, 86 AccountVideoRateModel,
83 UserModel, 87 UserModel,
84 VideoAbuseModel, 88 VideoAbuseModel,
89 VideoModel,
85 VideoChangeOwnershipModel, 90 VideoChangeOwnershipModel,
86 VideoChannelModel, 91 VideoChannelModel,
87 VideoShareModel, 92 VideoShareModel,
@@ -89,7 +94,6 @@ async function initDatabaseModels (silent: boolean) {
89 VideoCaptionModel, 94 VideoCaptionModel,
90 VideoBlacklistModel, 95 VideoBlacklistModel,
91 VideoTagModel, 96 VideoTagModel,
92 VideoModel,
93 VideoCommentModel, 97 VideoCommentModel,
94 ScheduleVideoUpdateModel, 98 ScheduleVideoUpdateModel,
95 VideoImportModel, 99 VideoImportModel,
@@ -99,7 +103,11 @@ async function initDatabaseModels (silent: boolean) {
99 AccountBlocklistModel, 103 AccountBlocklistModel,
100 ServerBlocklistModel, 104 ServerBlocklistModel,
101 UserNotificationModel, 105 UserNotificationModel,
102 UserNotificationSettingModel 106 UserNotificationSettingModel,
107 VideoStreamingPlaylistModel,
108 VideoPlaylistModel,
109 VideoPlaylistElementModel,
110 ThumbnailModel
103 ]) 111 ])
104 112
105 // Check extensions exist in the database 113 // Check extensions exist in the database
@@ -132,11 +140,16 @@ async function checkPostgresExtensions () {
132} 140}
133 141
134async function checkPostgresExtension (extension: string) { 142async function checkPostgresExtension (extension: string) {
135 const query = `SELECT true AS enabled FROM pg_available_extensions WHERE name = '${extension}' AND installed_version IS NOT NULL;` 143 const query = `SELECT 1 FROM pg_available_extensions WHERE name = '${extension}' AND installed_version IS NOT NULL;`
136 const [ res ] = await sequelizeTypescript.query(query, { raw: true }) 144 const options = {
145 type: QueryTypes.SELECT as QueryTypes.SELECT,
146 raw: true
147 }
148
149 const res = await sequelizeTypescript.query<object>(query, options)
137 150
138 if (!res || res.length === 0 || res[ 0 ][ 'enabled' ] !== true) { 151 if (!res || res.length === 0) {
139 // Try to create the extension ourself 152 // Try to create the extension ourselves
140 try { 153 try {
141 await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true }) 154 await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true })
142 155
diff --git a/server/initializers/index.ts b/server/initializers/index.ts
index fe9190a9c..0fc1a7363 100644
--- a/server/initializers/index.ts
+++ b/server/initializers/index.ts
@@ -1,5 +1,3 @@
1// Constants first, database in second!
2export * from './constants'
3export * from './database' 1export * from './database'
4export * from './installer' 2export * from './installer'
5export * from './migrator' 3export * from './migrator'
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts
index b9a9da183..127449577 100644
--- a/server/initializers/installer.ts
+++ b/server/initializers/installer.ts
@@ -1,14 +1,15 @@
1import * as passwordGenerator from 'password-generator' 1import * as passwordGenerator from 'password-generator'
2import { UserRole } from '../../shared' 2import { UserRole } from '../../shared'
3import { logger } from '../helpers/logger' 3import { logger } from '../helpers/logger'
4import { createApplicationActor, createUserAccountAndChannel } from '../lib/user' 4import { createApplicationActor, createUserAccountAndChannelAndPlaylist } from '../lib/user'
5import { UserModel } from '../models/account/user' 5import { 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 { FILES_CACHE, HLS_STREAMING_PLAYLIST_DIRECTORY, LAST_MIGRATION_VERSION } from './constants'
10import { sequelizeTypescript } from './database' 10import { sequelizeTypescript } from './database'
11import { remove, ensureDir } from 'fs-extra' 11import { ensureDir, remove } from 'fs-extra'
12import { CONFIG } from './config'
12 13
13async function installApplication () { 14async function installApplication () {
14 try { 15 try {
@@ -24,7 +25,7 @@ async function installApplication () {
24 }), 25 }),
25 26
26 // Directories 27 // Directories
27 removeCacheDirectories() 28 removeCacheAndTmpDirectories()
28 .then(() => createDirectoriesIfNotExist()) 29 .then(() => createDirectoriesIfNotExist())
29 ]) 30 ])
30 } catch (err) { 31 } catch (err) {
@@ -41,9 +42,9 @@ export {
41 42
42// --------------------------------------------------------------------------- 43// ---------------------------------------------------------------------------
43 44
44function removeCacheDirectories () { 45function removeCacheAndTmpDirectories () {
45 const cacheDirectories = Object.keys(CACHE) 46 const cacheDirectories = Object.keys(FILES_CACHE)
46 .map(k => CACHE[k].DIRECTORY) 47 .map(k => FILES_CACHE[k].DIRECTORY)
47 48
48 const tasks: Promise<any>[] = [] 49 const tasks: Promise<any>[] = []
49 50
@@ -53,13 +54,15 @@ function removeCacheDirectories () {
53 tasks.push(remove(dir)) 54 tasks.push(remove(dir))
54 } 55 }
55 56
57 tasks.push(remove(CONFIG.STORAGE.TMP_DIR))
58
56 return Promise.all(tasks) 59 return Promise.all(tasks)
57} 60}
58 61
59function createDirectoriesIfNotExist () { 62function createDirectoriesIfNotExist () {
60 const storage = CONFIG.STORAGE 63 const storage = CONFIG.STORAGE
61 const cacheDirectories = Object.keys(CACHE) 64 const cacheDirectories = Object.keys(FILES_CACHE)
62 .map(k => CACHE[k].DIRECTORY) 65 .map(k => FILES_CACHE[k].DIRECTORY)
63 66
64 const tasks: Promise<void>[] = [] 67 const tasks: Promise<void>[] = []
65 for (const key of Object.keys(storage)) { 68 for (const key of Object.keys(storage)) {
@@ -73,6 +76,9 @@ function createDirectoriesIfNotExist () {
73 tasks.push(ensureDir(dir)) 76 tasks.push(ensureDir(dir))
74 } 77 }
75 78
79 // Playlist directories
80 tasks.push(ensureDir(HLS_STREAMING_PLAYLIST_DIRECTORY))
81
76 return Promise.all(tasks) 82 return Promise.all(tasks)
77} 83}
78 84
@@ -138,7 +144,7 @@ async function createOAuthAdminIfNotExist () {
138 } 144 }
139 const user = new UserModel(userData) 145 const user = new UserModel(userData)
140 146
141 await createUserAccountAndChannel(user, validatePassword) 147 await createUserAccountAndChannelAndPlaylist(user, validatePassword)
142 logger.info('Username: ' + username) 148 logger.info('Username: ' + username)
143 logger.info('User password: ' + password) 149 logger.info('User password: ' + password)
144} 150}
diff --git a/server/initializers/migrations/0075-video-resolutions.ts b/server/initializers/migrations/0075-video-resolutions.ts
index 26a188e5e..e4f26cb77 100644
--- a/server/initializers/migrations/0075-video-resolutions.ts
+++ b/server/initializers/migrations/0075-video-resolutions.ts
@@ -1,6 +1,6 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { join } from 'path' 2import { join } from 'path'
3import { CONFIG } from '../../initializers/constants' 3import { CONFIG } from '../../initializers/config'
4import { getVideoFileResolution } from '../../helpers/ffmpeg-utils' 4import { getVideoFileResolution } from '../../helpers/ffmpeg-utils'
5import { readdir, rename } from 'fs-extra' 5import { readdir, rename } from 'fs-extra'
6 6
diff --git a/server/initializers/migrations/0080-video-channels.ts b/server/initializers/migrations/0080-video-channels.ts
index f19721517..5512bdcf1 100644
--- a/server/initializers/migrations/0080-video-channels.ts
+++ b/server/initializers/migrations/0080-video-channels.ts
@@ -69,12 +69,12 @@ async function up (utils: {
69 const options = { 69 const options = {
70 type: Sequelize.QueryTypes.SELECT 70 type: Sequelize.QueryTypes.SELECT
71 } 71 }
72 const rawVideos = await utils.sequelize.query(query, options) 72 const rawVideos = await utils.sequelize.query(query, options) as any
73 73
74 for (const rawVideo of rawVideos) { 74 for (const rawVideo of rawVideos) {
75 const videoChannel = await utils.db.VideoChannel.findOne({ where: { authorId: rawVideo.authorId } }) 75 const videoChannel = await utils.db.VideoChannel.findOne({ where: { authorId: rawVideo.authorId } })
76 76
77 const video = await utils.db.Video.findById(rawVideo.id) 77 const video = await utils.db.Video.findByPk(rawVideo.id)
78 video.channelId = videoChannel.id 78 video.channelId = videoChannel.id
79 await video.save() 79 await video.save()
80 } 80 }
diff --git a/server/initializers/migrations/0100-activitypub.ts b/server/initializers/migrations/0100-activitypub.ts
index a7ebd804c..2880a97d9 100644
--- a/server/initializers/migrations/0100-activitypub.ts
+++ b/server/initializers/migrations/0100-activitypub.ts
@@ -21,7 +21,7 @@ async function up (utils: {
21 const options = { 21 const options = {
22 type: Sequelize.QueryTypes.SELECT 22 type: Sequelize.QueryTypes.SELECT
23 } 23 }
24 const res = await utils.sequelize.query(query, options) 24 const res = await utils.sequelize.query(query, options) as any
25 25
26 if (!res[0] || res[0].total !== 0) { 26 if (!res[0] || res[0].total !== 0) {
27 throw new Error('You need to quit friends.') 27 throw new Error('You need to quit friends.')
@@ -68,8 +68,8 @@ async function up (utils: {
68 const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationInstance.id, undefined) 68 const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationInstance.id, undefined)
69 69
70 const { publicKey, privateKey } = await createPrivateAndPublicKeys() 70 const { publicKey, privateKey } = await createPrivateAndPublicKeys()
71 accountCreated.set('publicKey', publicKey) 71 accountCreated.Actor.publicKey = publicKey
72 accountCreated.set('privateKey', privateKey) 72 accountCreated.Actor.privateKey = privateKey
73 73
74 await accountCreated.save() 74 await accountCreated.save()
75 } 75 }
@@ -86,8 +86,8 @@ async function up (utils: {
86 const account = await createLocalAccountWithoutKeys(user.username, user.id, null, undefined) 86 const account = await createLocalAccountWithoutKeys(user.username, user.id, null, undefined)
87 87
88 const { publicKey, privateKey } = await createPrivateAndPublicKeys() 88 const { publicKey, privateKey } = await createPrivateAndPublicKeys()
89 account.set('publicKey', publicKey) 89 account.Actor.publicKey = publicKey
90 account.set('privateKey', privateKey) 90 account.Actor.privateKey = privateKey
91 await account.save() 91 await account.save()
92 } 92 }
93 93
diff --git a/server/initializers/migrations/0135-video-channel-actor.ts b/server/initializers/migrations/0135-video-channel-actor.ts
index 033f43b68..5ace0f4d2 100644
--- a/server/initializers/migrations/0135-video-channel-actor.ts
+++ b/server/initializers/migrations/0135-video-channel-actor.ts
@@ -239,7 +239,8 @@ async function up (utils: {
239 239
240 { 240 {
241 const query = 'SELECT * FROM "actor" WHERE "serverId" IS NULL AND "publicKey" IS NULL' 241 const query = 'SELECT * FROM "actor" WHERE "serverId" IS NULL AND "publicKey" IS NULL'
242 const [ res ] = await utils.sequelize.query(query) 242 const options = { type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT }
243 const [ res ] = await utils.sequelize.query(query, options)
243 244
244 for (const actor of res) { 245 for (const actor of res) {
245 const { privateKey, publicKey } = await createPrivateAndPublicKeys() 246 const { privateKey, publicKey } = await createPrivateAndPublicKeys()
diff --git a/server/initializers/migrations/0140-actor-url.ts b/server/initializers/migrations/0140-actor-url.ts
index e64ee3487..020499391 100644
--- a/server/initializers/migrations/0140-actor-url.ts
+++ b/server/initializers/migrations/0140-actor-url.ts
@@ -1,13 +1,13 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { CONFIG } from '../constants' 2import { WEBSERVER } from '../constants'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction,
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface,
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const toReplace = CONFIG.WEBSERVER.HOSTNAME + ':443' 9 const toReplace = WEBSERVER.HOSTNAME + ':443'
10 const by = CONFIG.WEBSERVER.HOST 10 const by = WEBSERVER.HOST
11 const replacer = column => `replace("${column}", '${toReplace}', '${by}')` 11 const replacer = column => `replace("${column}", '${toReplace}', '${by}')`
12 12
13 { 13 {
diff --git a/server/initializers/migrations/0170-actor-follow-score.ts b/server/initializers/migrations/0170-actor-follow-score.ts
index 2deabaf98..a12b35da9 100644
--- a/server/initializers/migrations/0170-actor-follow-score.ts
+++ b/server/initializers/migrations/0170-actor-follow-score.ts
@@ -1,5 +1,5 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { ACTOR_FOLLOW_SCORE } from '../index' 2import { ACTOR_FOLLOW_SCORE } from '../constants'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction,
diff --git a/server/initializers/migrations/0210-video-language.ts b/server/initializers/migrations/0210-video-language.ts
index b7ec90905..ca95c7527 100644
--- a/server/initializers/migrations/0210-video-language.ts
+++ b/server/initializers/migrations/0210-video-language.ts
@@ -1,5 +1,5 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../index' 2import { CONSTRAINTS_FIELDS } from '../constants'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction,
diff --git a/server/initializers/migrations/0215-video-support-length.ts b/server/initializers/migrations/0215-video-support-length.ts
index 994eda60d..ba395050f 100644
--- a/server/initializers/migrations/0215-video-support-length.ts
+++ b/server/initializers/migrations/0215-video-support-length.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,
diff --git a/server/initializers/migrations/0235-delete-some-video-indexes.ts b/server/initializers/migrations/0235-delete-some-video-indexes.ts
index e362f240c..5964b0dc5 100644
--- a/server/initializers/migrations/0235-delete-some-video-indexes.ts
+++ b/server/initializers/migrations/0235-delete-some-video-indexes.ts
@@ -1,8 +1,4 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { createClient } from 'redis'
3import { CONFIG } from '../constants'
4import { JobQueue } from '../../lib/job-queue'
5import { initDatabaseModels } from '../database'
6 2
7async function up (utils: { 3async function up (utils: {
8 transaction: Sequelize.Transaction 4 transaction: Sequelize.Transaction
diff --git a/server/initializers/migrations/0240-drop-old-indexes.ts b/server/initializers/migrations/0240-drop-old-indexes.ts
index ba961e3f9..39868fa2d 100644
--- a/server/initializers/migrations/0240-drop-old-indexes.ts
+++ b/server/initializers/migrations/0240-drop-old-indexes.ts
@@ -1,8 +1,4 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { createClient } from 'redis'
3import { CONFIG } from '../constants'
4import { JobQueue } from '../../lib/job-queue'
5import { initDatabaseModels } from '../database'
6 2
7async function up (utils: { 3async function up (utils: {
8 transaction: Sequelize.Transaction 4 transaction: Sequelize.Transaction
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}
diff --git a/server/initializers/migrations/0335-video-downloading-enabled.ts b/server/initializers/migrations/0335-video-downloading-enabled.ts
new file mode 100644
index 000000000..e79466447
--- /dev/null
+++ b/server/initializers/migrations/0335-video-downloading-enabled.ts
@@ -0,0 +1,27 @@
1import * as Sequelize from 'sequelize'
2import { Migration } from '../../models/migrations'
3
4async function up (utils: {
5 transaction: Sequelize.Transaction,
6 queryInterface: Sequelize.QueryInterface,
7 sequelize: Sequelize.Sequelize
8}): Promise<void> {
9 const data = {
10 type: Sequelize.BOOLEAN,
11 allowNull: false,
12 defaultValue: true
13 } as Migration.Boolean
14 await utils.queryInterface.addColumn('video', 'downloadEnabled', data)
15
16 data.defaultValue = null
17 return utils.queryInterface.changeColumn('video', 'downloadEnabled', data)
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/0340-add-originally-published-at.ts b/server/initializers/migrations/0340-add-originally-published-at.ts
new file mode 100644
index 000000000..fe4f4a5f9
--- /dev/null
+++ b/server/initializers/migrations/0340-add-originally-published-at.ts
@@ -0,0 +1,25 @@
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 const data = {
10 type: Sequelize.DATE,
11 allowNull: true,
12 defaultValue: null
13 }
14 await utils.queryInterface.addColumn('video', 'originallyPublishedAt', data)
15
16}
17
18function down (options) {
19 throw new Error('Not implemented.')
20}
21
22export {
23 up,
24 down
25}
diff --git a/server/initializers/migrations/0345-video-playlists.ts b/server/initializers/migrations/0345-video-playlists.ts
new file mode 100644
index 000000000..de69f5b9e
--- /dev/null
+++ b/server/initializers/migrations/0345-video-playlists.ts
@@ -0,0 +1,88 @@
1import * as Sequelize from 'sequelize'
2import { VideoPlaylistPrivacy, VideoPlaylistType } from '../../../shared/models/videos'
3import * as uuidv4 from 'uuid/v4'
4import { WEBSERVER } from '../constants'
5
6async function up (utils: {
7 transaction: Sequelize.Transaction,
8 queryInterface: Sequelize.QueryInterface,
9 sequelize: Sequelize.Sequelize
10}): Promise<void> {
11 const transaction = utils.transaction
12
13 {
14 const query = `
15CREATE TABLE IF NOT EXISTS "videoPlaylist"
16(
17 "id" SERIAL,
18 "name" VARCHAR(255) NOT NULL,
19 "description" VARCHAR(255),
20 "privacy" INTEGER NOT NULL,
21 "url" VARCHAR(2000) NOT NULL,
22 "uuid" UUID NOT NULL,
23 "type" INTEGER NOT NULL DEFAULT 1,
24 "ownerAccountId" INTEGER NOT NULL REFERENCES "account" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
25 "videoChannelId" INTEGER REFERENCES "videoChannel" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
26 "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
27 "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
28 PRIMARY KEY ("id")
29);`
30 await utils.sequelize.query(query, { transaction })
31 }
32
33 {
34 const query = `
35CREATE TABLE IF NOT EXISTS "videoPlaylistElement"
36(
37 "id" SERIAL,
38 "url" VARCHAR(2000) NOT NULL,
39 "position" INTEGER NOT NULL DEFAULT 1,
40 "startTimestamp" INTEGER,
41 "stopTimestamp" INTEGER,
42 "videoPlaylistId" INTEGER NOT NULL REFERENCES "videoPlaylist" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
43 "videoId" INTEGER NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
44 "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
45 "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
46 PRIMARY KEY ("id")
47);`
48
49 await utils.sequelize.query(query, { transaction })
50 }
51
52 {
53 const userQuery = 'SELECT "username" FROM "user";'
54
55 const options = { transaction, type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT }
56 const userResult = await utils.sequelize.query<{ username: string }>(userQuery, options)
57 const usernames = userResult.map(r => r.username)
58
59 for (const username of usernames) {
60 const uuid = uuidv4()
61
62 const baseUrl = WEBSERVER.URL + '/video-playlists/' + uuid
63 const query = `
64 INSERT INTO "videoPlaylist" ("url", "uuid", "name", "privacy", "type", "ownerAccountId", "createdAt", "updatedAt")
65 SELECT '${baseUrl}' AS "url",
66 '${uuid}' AS "uuid",
67 'Watch later' AS "name",
68 ${VideoPlaylistPrivacy.PRIVATE} AS "privacy",
69 ${VideoPlaylistType.WATCH_LATER} AS "type",
70 "account"."id" AS "ownerAccountId",
71 NOW() as "createdAt",
72 NOW() as "updatedAt"
73 FROM "user" INNER JOIN "account" ON "user"."id" = "account"."userId"
74 WHERE "user"."username" = '${username}'`
75
76 await utils.sequelize.query(query, { transaction })
77 }
78 }
79}
80
81function down (options) {
82 throw new Error('Not implemented.')
83}
84
85export {
86 up,
87 down
88}
diff --git a/server/initializers/migrations/0350-video-blacklist-type.ts b/server/initializers/migrations/0350-video-blacklist-type.ts
new file mode 100644
index 000000000..4849020ef
--- /dev/null
+++ b/server/initializers/migrations/0350-video-blacklist-type.ts
@@ -0,0 +1,64 @@
1import * as Sequelize from 'sequelize'
2import { VideoBlacklistType } from '../../../shared/models/videos'
3
4async function up (utils: {
5 transaction: Sequelize.Transaction,
6 queryInterface: Sequelize.QueryInterface,
7 sequelize: Sequelize.Sequelize,
8 db: any
9}): Promise<void> {
10 {
11 const data = {
12 type: Sequelize.INTEGER,
13 allowNull: true,
14 defaultValue: null
15 }
16
17 await utils.queryInterface.addColumn('videoBlacklist', 'type', data)
18 }
19
20 {
21 const query = 'UPDATE "videoBlacklist" SET "type" = ' + VideoBlacklistType.MANUAL
22 await utils.sequelize.query(query)
23 }
24
25 {
26 const data = {
27 type: Sequelize.INTEGER,
28 allowNull: false,
29 defaultValue: null
30 }
31 await utils.queryInterface.changeColumn('videoBlacklist', 'type', data)
32 }
33
34 {
35 const data = {
36 type: Sequelize.INTEGER,
37 defaultValue: null,
38 allowNull: true
39 }
40 await utils.queryInterface.addColumn('userNotificationSetting', 'videoAutoBlacklistAsModerator', data)
41 }
42
43 {
44 const query = 'UPDATE "userNotificationSetting" SET "videoAutoBlacklistAsModerator" = 3'
45 await utils.sequelize.query(query)
46 }
47
48 {
49 const data = {
50 type: Sequelize.INTEGER,
51 defaultValue: null,
52 allowNull: false
53 }
54 await utils.queryInterface.changeColumn('userNotificationSetting', 'videoAutoBlacklistAsModerator', data)
55 }
56}
57function down (options) {
58 throw new Error('Not implemented.')
59}
60
61export {
62 up,
63 down
64}
diff --git a/server/initializers/migrations/0355-p2p-peer-version.ts b/server/initializers/migrations/0355-p2p-peer-version.ts
new file mode 100644
index 000000000..18f23d9b7
--- /dev/null
+++ b/server/initializers/migrations/0355-p2p-peer-version.ts
@@ -0,0 +1,41 @@
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 {
11 const data = {
12 type: Sequelize.INTEGER,
13 allowNull: true,
14 defaultValue: null
15 }
16 await utils.queryInterface.addColumn('videoStreamingPlaylist', 'p2pMediaLoaderPeerVersion', data)
17 }
18
19 {
20 const query = `UPDATE "videoStreamingPlaylist" SET "p2pMediaLoaderPeerVersion" = 0;`
21 await utils.sequelize.query(query)
22 }
23
24 {
25 const data = {
26 type: Sequelize.INTEGER,
27 allowNull: false,
28 defaultValue: null
29 }
30 await utils.queryInterface.changeColumn('videoStreamingPlaylist', 'p2pMediaLoaderPeerVersion', data)
31 }
32}
33
34function down (options) {
35 throw new Error('Not implemented.')
36}
37
38export {
39 up,
40 down
41}
diff --git a/server/initializers/migrations/0360-notification-instance-follower.ts b/server/initializers/migrations/0360-notification-instance-follower.ts
new file mode 100644
index 000000000..05caf8e1d
--- /dev/null
+++ b/server/initializers/migrations/0360-notification-instance-follower.ts
@@ -0,0 +1,40 @@
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.INTEGER,
12 defaultValue: null,
13 allowNull: true
14 }
15 await utils.queryInterface.addColumn('userNotificationSetting', 'newInstanceFollower', data)
16 }
17
18 {
19 const query = 'UPDATE "userNotificationSetting" SET "newInstanceFollower" = 1'
20 await utils.sequelize.query(query)
21 }
22
23 {
24 const data = {
25 type: Sequelize.INTEGER,
26 defaultValue: null,
27 allowNull: false
28 }
29 await utils.queryInterface.changeColumn('userNotificationSetting', 'newInstanceFollower', data)
30 }
31}
32
33function down (options) {
34 throw new Error('Not implemented.')
35}
36
37export {
38 up,
39 down
40}
diff --git a/server/initializers/migrations/0365-user-admin-flags.ts b/server/initializers/migrations/0365-user-admin-flags.ts
new file mode 100644
index 000000000..20553100a
--- /dev/null
+++ b/server/initializers/migrations/0365-user-admin-flags.ts
@@ -0,0 +1,40 @@
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.INTEGER,
12 defaultValue: null,
13 allowNull: true
14 }
15 await utils.queryInterface.addColumn('user', 'adminFlags', data)
16 }
17
18 {
19 const query = 'UPDATE "user" SET "adminFlags" = 0'
20 await utils.sequelize.query(query)
21 }
22
23 {
24 const data = {
25 type: Sequelize.INTEGER,
26 defaultValue: null,
27 allowNull: false
28 }
29 await utils.queryInterface.changeColumn('user', 'adminFlags', data)
30 }
31}
32
33function down (options) {
34 throw new Error('Not implemented.')
35}
36
37export {
38 up,
39 down
40}
diff --git a/server/initializers/migrations/0370-thumbnail.ts b/server/initializers/migrations/0370-thumbnail.ts
new file mode 100644
index 000000000..384ca1a15
--- /dev/null
+++ b/server/initializers/migrations/0370-thumbnail.ts
@@ -0,0 +1,50 @@
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 = `
11CREATE TABLE IF NOT EXISTS "thumbnail"
12(
13 "id" SERIAL,
14 "filename" VARCHAR(255) NOT NULL,
15 "height" INTEGER DEFAULT NULL,
16 "width" INTEGER DEFAULT NULL,
17 "type" INTEGER NOT NULL,
18 "fileUrl" VARCHAR(255),
19 "videoId" INTEGER REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
20 "videoPlaylistId" INTEGER REFERENCES "videoPlaylist" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
21 "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
22 "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
23 PRIMARY KEY ("id")
24);`
25 await utils.sequelize.query(query)
26 }
27
28 {
29 // All video thumbnails
30 const query = 'INSERT INTO "thumbnail" ("filename", "type", "videoId", "height", "width", "createdAt", "updatedAt")' +
31 'SELECT uuid || \'.jpg\', 1, id, 110, 200, NOW(), NOW() FROM "video"'
32 await utils.sequelize.query(query)
33 }
34
35 {
36 // All video previews
37 const query = 'INSERT INTO "thumbnail" ("filename", "type", "videoId", "height", "width", "createdAt", "updatedAt")' +
38 'SELECT uuid || \'.jpg\', 2, id, 315, 560, NOW(), NOW() FROM "video"'
39 await utils.sequelize.query(query)
40 }
41}
42
43function down (options) {
44 throw new Error('Not implemented.')
45}
46
47export {
48 up,
49 down
50}
diff --git a/server/initializers/migrations/0375-account-description.ts b/server/initializers/migrations/0375-account-description.ts
new file mode 100644
index 000000000..1258563fd
--- /dev/null
+++ b/server/initializers/migrations/0375-account-description.ts
@@ -0,0 +1,25 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize,
7 db: any
8}): Promise<void> {
9 const data = {
10 type: Sequelize.STRING(1000),
11 allowNull: true,
12 defaultValue: null
13 }
14
15 await utils.queryInterface.changeColumn('account', 'description', data)
16}
17
18function down (options) {
19 throw new Error('Not implemented.')
20}
21
22export {
23 up,
24 down
25}
diff --git a/server/initializers/migrator.ts b/server/initializers/migrator.ts
index adc2f9fb3..1cb0116b7 100644
--- a/server/initializers/migrator.ts
+++ b/server/initializers/migrator.ts
@@ -3,6 +3,7 @@ import { logger } from '../helpers/logger'
3import { LAST_MIGRATION_VERSION } from './constants' 3import { LAST_MIGRATION_VERSION } from './constants'
4import { sequelizeTypescript } from './database' 4import { sequelizeTypescript } from './database'
5import { readdir } from 'fs-extra' 5import { readdir } from 'fs-extra'
6import { QueryTypes } from 'sequelize'
6 7
7async function migrate () { 8async function migrate () {
8 const tables = await sequelizeTypescript.getQueryInterface().showAllTables() 9 const tables = await sequelizeTypescript.getQueryInterface().showAllTables()
@@ -13,7 +14,12 @@ async function migrate () {
13 14
14 let actualVersion: number | null = null 15 let actualVersion: number | null = null
15 16
16 const [ rows ] = await sequelizeTypescript.query('SELECT "migrationVersion" FROM "application"') 17 const query = 'SELECT "migrationVersion" FROM "application"'
18 const options = {
19 type: QueryTypes.SELECT as QueryTypes.SELECT
20 }
21
22 const rows = await sequelizeTypescript.query<{ migrationVersion: number }>(query, options)
17 if (rows && rows[0] && rows[0].migrationVersion) { 23 if (rows && rows[0] && rows[0].migrationVersion) {
18 actualVersion = rows[0].migrationVersion 24 actualVersion = rows[0].migrationVersion
19 } 25 }