]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/initializers/constants.ts
Add redis cache to feed route
[github/Chocobozzz/PeerTube.git] / server / initializers / constants.ts
1 import { IConfig } from 'config'
2 import { dirname, join } from 'path'
3 import { JobType, VideoRateType } from '../../shared/models'
4 import { ActivityPubActorType } from '../../shared/models/activitypub'
5 import { FollowState } from '../../shared/models/actors'
6 import { VideoPrivacy } from '../../shared/models/videos'
7 // Do not use barrels, remain constants as independent as possible
8 import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils'
9
10 // Use a variable to reload the configuration if we need
11 let config: IConfig = require('config')
12
13 // ---------------------------------------------------------------------------
14
15 const LAST_MIGRATION_VERSION = 200
16
17 // ---------------------------------------------------------------------------
18
19 // API version
20 const API_VERSION = 'v1'
21
22 // Number of results by default for the pagination
23 const PAGINATION_COUNT_DEFAULT = 15
24
25 // Sortable columns per schema
26 const SORTABLE_COLUMNS = {
27 USERS: [ 'id', 'username', 'createdAt' ],
28 ACCOUNTS: [ 'createdAt' ],
29 JOBS: [ 'createdAt' ],
30 VIDEO_ABUSES: [ 'id', 'createdAt' ],
31 VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
32 VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ],
33 VIDEO_COMMENT_THREADS: [ 'createdAt' ],
34 BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ],
35 FOLLOWERS: [ 'createdAt' ],
36 FOLLOWING: [ 'createdAt' ]
37 }
38
39 const OAUTH_LIFETIME = {
40 ACCESS_TOKEN: 3600 * 4, // 4 hours
41 REFRESH_TOKEN: 1209600 // 2 weeks
42 }
43
44 // ---------------------------------------------------------------------------
45
46 // Number of points we add/remove after a successful/bad request
47 const ACTOR_FOLLOW_SCORE = {
48 PENALTY: -10,
49 BONUS: 10,
50 BASE: 1000,
51 MAX: 10000
52 }
53
54 const FOLLOW_STATES: { [ id: string ]: FollowState } = {
55 PENDING: 'pending',
56 ACCEPTED: 'accepted'
57 }
58
59 const REMOTE_SCHEME = {
60 HTTP: 'https',
61 WS: 'wss'
62 }
63
64 const JOB_ATTEMPTS: { [ id in JobType ]: number } = {
65 'activitypub-http-broadcast': 5,
66 'activitypub-http-unicast': 5,
67 'activitypub-http-fetcher': 5,
68 'video-file': 1,
69 'email': 5
70 }
71 const JOB_CONCURRENCY: { [ id in JobType ]: number } = {
72 'activitypub-http-broadcast': 1,
73 'activitypub-http-unicast': 5,
74 'activitypub-http-fetcher': 1,
75 'video-file': 1,
76 'email': 5
77 }
78 // 2 days
79 const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2
80
81 // 1 hour
82 let SCHEDULER_INTERVAL = 60000 * 60
83
84 // ---------------------------------------------------------------------------
85
86 const CONFIG = {
87 CUSTOM_FILE: getLocalConfigFilePath(),
88 LISTEN: {
89 PORT: config.get<number>('listen.port'),
90 HOSTNAME: config.get<string>('listen.hostname')
91 },
92 DATABASE: {
93 DBNAME: 'peertube' + config.get<string>('database.suffix'),
94 HOSTNAME: config.get<string>('database.hostname'),
95 PORT: config.get<number>('database.port'),
96 USERNAME: config.get<string>('database.username'),
97 PASSWORD: config.get<string>('database.password')
98 },
99 REDIS: {
100 HOSTNAME: config.get<string>('redis.hostname'),
101 PORT: config.get<number>('redis.port'),
102 AUTH: config.get<string>('redis.auth')
103 },
104 SMTP: {
105 HOSTNAME: config.get<string>('smtp.hostname'),
106 PORT: config.get<number>('smtp.port'),
107 USERNAME: config.get<string>('smtp.username'),
108 PASSWORD: config.get<string>('smtp.password'),
109 TLS: config.get<boolean>('smtp.tls'),
110 DISABLE_STARTTLS: config.get<boolean>('smtp.disable_starttls'),
111 CA_FILE: config.get<string>('smtp.ca_file'),
112 FROM_ADDRESS: config.get<string>('smtp.from_address')
113 },
114 STORAGE: {
115 AVATARS_DIR: buildPath(config.get<string>('storage.avatars')),
116 LOG_DIR: buildPath(config.get<string>('storage.logs')),
117 VIDEOS_DIR: buildPath(config.get<string>('storage.videos')),
118 THUMBNAILS_DIR: buildPath(config.get<string>('storage.thumbnails')),
119 PREVIEWS_DIR: buildPath(config.get<string>('storage.previews')),
120 TORRENTS_DIR: buildPath(config.get<string>('storage.torrents')),
121 CACHE_DIR: buildPath(config.get<string>('storage.cache'))
122 },
123 WEBSERVER: {
124 SCHEME: config.get<boolean>('webserver.https') === true ? 'https' : 'http',
125 WS: config.get<boolean>('webserver.https') === true ? 'wss' : 'ws',
126 HOSTNAME: config.get<string>('webserver.hostname'),
127 PORT: config.get<number>('webserver.port'),
128 URL: '',
129 HOST: ''
130 },
131 TRUST_PROXY: config.get<string[]>('trust_proxy'),
132 LOG: {
133 LEVEL: config.get<string>('log.level')
134 },
135 ADMIN: {
136 get EMAIL () { return config.get<string>('admin.email') }
137 },
138 SIGNUP: {
139 get ENABLED () { return config.get<boolean>('signup.enabled') },
140 get LIMIT () { return config.get<number>('signup.limit') }
141 },
142 USER: {
143 get VIDEO_QUOTA () { return config.get<number>('user.video_quota') }
144 },
145 TRANSCODING: {
146 get ENABLED () { return config.get<boolean>('transcoding.enabled') },
147 get THREADS () { return config.get<number>('transcoding.threads') },
148 RESOLUTIONS: {
149 get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
150 get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') },
151 get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') },
152 get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') },
153 get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') }
154 }
155 },
156 CACHE: {
157 PREVIEWS: {
158 get SIZE () { return config.get<number>('cache.previews.size') }
159 }
160 },
161 INSTANCE: {
162 get NAME () { return config.get<string>('instance.name') },
163 get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') },
164 get DESCRIPTION () { return config.get<string>('instance.description') },
165 get TERMS () { return config.get<string>('instance.terms') },
166 get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') },
167 CUSTOMIZATIONS: {
168 get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
169 get CSS () { return config.get<string>('instance.customizations.css') }
170 }
171 }
172 }
173
174 // ---------------------------------------------------------------------------
175
176 const CONSTRAINTS_FIELDS = {
177 USERS: {
178 USERNAME: { min: 3, max: 20 }, // Length
179 PASSWORD: { min: 6, max: 255 }, // Length
180 DESCRIPTION: { min: 3, max: 250 }, // Length
181 VIDEO_QUOTA: { min: -1 }
182 },
183 VIDEO_ABUSES: {
184 REASON: { min: 2, max: 300 } // Length
185 },
186 VIDEO_CHANNELS: {
187 NAME: { min: 3, max: 120 }, // Length
188 DESCRIPTION: { min: 3, max: 250 }, // Length
189 SUPPORT: { min: 3, max: 300 }, // Length
190 URL: { min: 3, max: 2000 } // Length
191 },
192 VIDEOS: {
193 NAME: { min: 3, max: 120 }, // Length
194 TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length
195 DESCRIPTION: { min: 3, max: 10000 }, // Length
196 SUPPORT: { min: 3, max: 300 }, // Length
197 IMAGE: {
198 EXTNAME: [ '.jpg', '.jpeg' ],
199 FILE_SIZE: {
200 max: 2 * 1024 * 1024 // 2MB
201 }
202 },
203 EXTNAME: [ '.mp4', '.ogv', '.webm' ],
204 INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
205 DURATION: { min: 1 }, // Number
206 TAGS: { min: 0, max: 5 }, // Number of total tags
207 TAG: { min: 2, max: 30 }, // Length
208 THUMBNAIL: { min: 2, max: 30 },
209 THUMBNAIL_DATA: { min: 0, max: 20000 }, // Bytes
210 VIEWS: { min: 0 },
211 LIKES: { min: 0 },
212 DISLIKES: { min: 0 },
213 FILE_SIZE: { min: 10 },
214 URL: { min: 3, max: 2000 } // Length
215 },
216 ACTORS: {
217 PUBLIC_KEY: { min: 10, max: 5000 }, // Length
218 PRIVATE_KEY: { min: 10, max: 5000 }, // Length
219 URL: { min: 3, max: 2000 }, // Length
220 AVATAR: {
221 EXTNAME: [ '.png', '.jpeg', '.jpg' ],
222 FILE_SIZE: {
223 max: 2 * 1024 * 1024 // 2MB
224 }
225 }
226 },
227 VIDEO_EVENTS: {
228 COUNT: { min: 0 }
229 },
230 VIDEO_COMMENTS: {
231 TEXT: { min: 1, max: 3000 }, // Length
232 URL: { min: 3, max: 2000 } // Length
233 },
234 VIDEO_SHARE: {
235 URL: { min: 3, max: 2000 } // Length
236 }
237 }
238
239 const RATES_LIMIT = {
240 LOGIN: {
241 WINDOW_MS: 5 * 60 * 1000, // 5 minutes
242 MAX: 15 // 15 attempts
243 }
244 }
245
246 let VIDEO_VIEW_LIFETIME = 60000 * 60 // 1 hour
247 const VIDEO_TRANSCODING_FPS = {
248 MIN: 10,
249 MAX: 30
250 }
251
252 const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = {
253 LIKE: 'like',
254 DISLIKE: 'dislike'
255 }
256
257 const VIDEO_CATEGORIES = {
258 1: 'Music',
259 2: 'Films',
260 3: 'Vehicles',
261 4: 'Art',
262 5: 'Sports',
263 6: 'Travels',
264 7: 'Gaming',
265 8: 'People',
266 9: 'Comedy',
267 10: 'Entertainment',
268 11: 'News',
269 12: 'How To',
270 13: 'Education',
271 14: 'Activism',
272 15: 'Science & Technology',
273 16: 'Animals',
274 17: 'Kids',
275 18: 'Food'
276 }
277
278 // See https://creativecommons.org/licenses/?lang=en
279 const VIDEO_LICENCES = {
280 1: 'Attribution',
281 2: 'Attribution - Share Alike',
282 3: 'Attribution - No Derivatives',
283 4: 'Attribution - Non Commercial',
284 5: 'Attribution - Non Commercial - Share Alike',
285 6: 'Attribution - Non Commercial - No Derivatives',
286 7: 'Public Domain Dedication'
287 }
288
289 // See https://en.wikipedia.org/wiki/List_of_languages_by_number_of_native_speakers#Nationalencyklopedin
290 const VIDEO_LANGUAGES = {
291 1: 'English',
292 2: 'Spanish',
293 3: 'Mandarin',
294 4: 'Hindi',
295 5: 'Arabic',
296 6: 'Portuguese',
297 7: 'Bengali',
298 8: 'Russian',
299 9: 'Japanese',
300 10: 'Punjabi',
301 11: 'German',
302 12: 'Korean',
303 13: 'French',
304 14: 'Italian',
305 1000: 'Sign Language',
306 1001: 'American Sign Language',
307 1002: 'Arab Sign Language',
308 1003: 'British Sign Language',
309 1004: 'Brazilian Sign Language',
310 1005: 'Chinese Sign Language',
311 1006: 'Czech Sign Language',
312 1007: 'Danish Sign Language',
313 1008: 'French Sign Language',
314 1009: 'German Sign Language',
315 1010: 'Indo-Pakistani Sign Language',
316 1011: 'Japanese Sign Language',
317 1012: 'South African Sign Language',
318 1013: 'Swedish Sign Language',
319 1014: 'Russian Sign Language'
320 }
321
322 const VIDEO_PRIVACIES = {
323 [VideoPrivacy.PUBLIC]: 'Public',
324 [VideoPrivacy.UNLISTED]: 'Unlisted',
325 [VideoPrivacy.PRIVATE]: 'Private'
326 }
327
328 const VIDEO_MIMETYPE_EXT = {
329 'video/webm': '.webm',
330 'video/ogg': '.ogv',
331 'video/mp4': '.mp4'
332 }
333
334 const IMAGE_MIMETYPE_EXT = {
335 'image/png': '.png',
336 'image/jpg': '.jpg',
337 'image/jpeg': '.jpg'
338 }
339
340 // ---------------------------------------------------------------------------
341
342 const SERVER_ACTOR_NAME = 'peertube'
343
344 const ACTIVITY_PUB = {
345 POTENTIAL_ACCEPT_HEADERS: [
346 'application/activity+json',
347 'application/ld+json',
348 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
349 ],
350 ACCEPT_HEADER: 'application/activity+json, application/ld+json',
351 PUBLIC: 'https://www.w3.org/ns/activitystreams#Public',
352 COLLECTION_ITEMS_PER_PAGE: 10,
353 FETCH_PAGE_LIMIT: 100,
354 URL_MIME_TYPES: {
355 VIDEO: Object.keys(VIDEO_MIMETYPE_EXT),
356 TORRENT: [ 'application/x-bittorrent' ],
357 MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
358 },
359 MAX_RECURSION_COMMENTS: 100,
360 ACTOR_REFRESH_INTERVAL: 3600 * 24 * 1000 // 1 day
361 }
362
363 const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
364 GROUP: 'Group',
365 PERSON: 'Person',
366 APPLICATION: 'Application'
367 }
368
369 // ---------------------------------------------------------------------------
370
371 const PRIVATE_RSA_KEY_SIZE = 2048
372
373 // Password encryption
374 const BCRYPT_SALT_SIZE = 10
375
376 const USER_PASSWORD_RESET_LIFETIME = 60000 * 5 // 5 minutes
377
378 // ---------------------------------------------------------------------------
379
380 // Express static paths (router)
381 const STATIC_PATHS = {
382 PREVIEWS: '/static/previews/',
383 THUMBNAILS: '/static/thumbnails/',
384 TORRENTS: '/static/torrents/',
385 WEBSEED: '/static/webseed/',
386 AVATARS: '/static/avatars/'
387 }
388
389 // Cache control
390 let STATIC_MAX_AGE = '30d'
391
392 // Videos thumbnail size
393 const THUMBNAILS_SIZE = {
394 width: 200,
395 height: 110
396 }
397 const PREVIEWS_SIZE = {
398 width: 560,
399 height: 315
400 }
401 const AVATARS_SIZE = {
402 width: 120,
403 height: 120
404 }
405
406 const EMBED_SIZE = {
407 width: 560,
408 height: 315
409 }
410
411 // Sub folders of cache directory
412 const CACHE = {
413 DIRECTORIES: {
414 PREVIEWS: join(CONFIG.STORAGE.CACHE_DIR, 'previews')
415 }
416 }
417
418 const ACCEPT_HEADERS = [ 'html', 'application/json' ].concat(ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS)
419
420 // ---------------------------------------------------------------------------
421
422 const OPENGRAPH_AND_OEMBED_COMMENT = '<!-- open graph and oembed tags -->'
423
424 // ---------------------------------------------------------------------------
425
426 const FEEDS = {
427 COUNT: 20,
428 CACHE_LIFETIME: 1000 * 60 * 15 // 15 minutes
429 }
430
431 // ---------------------------------------------------------------------------
432
433 // Special constants for a test instance
434 if (isTestInstance() === true) {
435 ACTOR_FOLLOW_SCORE.BASE = 20
436 REMOTE_SCHEME.HTTP = 'http'
437 REMOTE_SCHEME.WS = 'ws'
438 STATIC_MAX_AGE = '0'
439 ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE = 2
440 ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds
441 CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB
442 SCHEDULER_INTERVAL = 10000
443 VIDEO_VIEW_LIFETIME = 1000 // 1 second
444 }
445
446 updateWebserverConfig()
447
448 // ---------------------------------------------------------------------------
449
450 export {
451 API_VERSION,
452 AVATARS_SIZE,
453 ACCEPT_HEADERS,
454 BCRYPT_SALT_SIZE,
455 CACHE,
456 CONFIG,
457 CONSTRAINTS_FIELDS,
458 EMBED_SIZE,
459 JOB_CONCURRENCY,
460 JOB_ATTEMPTS,
461 LAST_MIGRATION_VERSION,
462 OAUTH_LIFETIME,
463 OPENGRAPH_AND_OEMBED_COMMENT,
464 PAGINATION_COUNT_DEFAULT,
465 ACTOR_FOLLOW_SCORE,
466 PREVIEWS_SIZE,
467 REMOTE_SCHEME,
468 FOLLOW_STATES,
469 SERVER_ACTOR_NAME,
470 PRIVATE_RSA_KEY_SIZE,
471 SORTABLE_COLUMNS,
472 FEEDS,
473 STATIC_MAX_AGE,
474 STATIC_PATHS,
475 ACTIVITY_PUB,
476 ACTIVITY_PUB_ACTOR_TYPES,
477 THUMBNAILS_SIZE,
478 VIDEO_CATEGORIES,
479 VIDEO_LANGUAGES,
480 VIDEO_PRIVACIES,
481 VIDEO_LICENCES,
482 VIDEO_RATE_TYPES,
483 VIDEO_MIMETYPE_EXT,
484 VIDEO_TRANSCODING_FPS,
485 USER_PASSWORD_RESET_LIFETIME,
486 IMAGE_MIMETYPE_EXT,
487 SCHEDULER_INTERVAL,
488 RATES_LIMIT,
489 JOB_COMPLETED_LIFETIME,
490 VIDEO_VIEW_LIFETIME
491 }
492
493 // ---------------------------------------------------------------------------
494
495 function getLocalConfigFilePath () {
496 const configSources = config.util.getConfigSources()
497 if (configSources.length === 0) throw new Error('Invalid config source.')
498
499 let filename = 'local'
500 if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
501 if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
502
503 return join(dirname(configSources[ 0 ].name), filename + '.json')
504 }
505
506 function updateWebserverConfig () {
507 CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT)
508 CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP)
509 }
510
511 export function reloadConfig () {
512
513 function directory () {
514 if (process.env.NODE_CONFIG_DIR) {
515 return process.env.NODE_CONFIG_DIR
516 }
517
518 return join(root(), 'config')
519 }
520
521 function purge () {
522 for (const fileName in require.cache) {
523 if (-1 === fileName.indexOf(directory())) {
524 continue
525 }
526
527 delete require.cache[fileName]
528 }
529
530 delete require.cache[require.resolve('config')]
531 }
532
533 purge()
534
535 config = require('config')
536
537 updateWebserverConfig()
538 }