Don't show videos of remote instance after unfollow
[oweals/peertube.git] / server / initializers / constants.ts
1 import { IConfig } from 'config'
2 import { dirname, join } from 'path'
3 import { JobCategory, JobState, 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 = 175
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: [ 'id', '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_STATES: { [ id: string ]: JobState } = {
65   PENDING: 'pending',
66   PROCESSING: 'processing',
67   ERROR: 'error',
68   SUCCESS: 'success'
69 }
70 const JOB_CATEGORIES: { [ id: string ]: JobCategory } = {
71   TRANSCODING: 'transcoding',
72   ACTIVITYPUB_HTTP: 'activitypub-http'
73 }
74 // How many maximum jobs we fetch from the database per cycle
75 const JOBS_FETCH_LIMIT_PER_CYCLE = {
76   transcoding: 10,
77   httpRequest: 20
78 }
79 // 1 minutes
80 let JOBS_FETCHING_INTERVAL = 60000
81
82 // 1 hour
83 let SCHEDULER_INTERVAL = 60000 * 60
84
85 // ---------------------------------------------------------------------------
86
87 const CONFIG = {
88   CUSTOM_FILE: getLocalConfigFilePath(),
89   LISTEN: {
90     PORT: config.get<number>('listen.port')
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   STORAGE: {
100     AVATARS_DIR: buildPath(config.get<string>('storage.avatars')),
101     LOG_DIR: buildPath(config.get<string>('storage.logs')),
102     VIDEOS_DIR: buildPath(config.get<string>('storage.videos')),
103     THUMBNAILS_DIR: buildPath(config.get<string>('storage.thumbnails')),
104     PREVIEWS_DIR: buildPath(config.get<string>('storage.previews')),
105     TORRENTS_DIR: buildPath(config.get<string>('storage.torrents')),
106     CACHE_DIR: buildPath(config.get<string>('storage.cache'))
107   },
108   WEBSERVER: {
109     SCHEME: config.get<boolean>('webserver.https') === true ? 'https' : 'http',
110     WS: config.get<boolean>('webserver.https') === true ? 'wss' : 'ws',
111     HOSTNAME: config.get<string>('webserver.hostname'),
112     PORT: config.get<number>('webserver.port'),
113     URL: '',
114     HOST: ''
115   },
116   ADMIN: {
117     get EMAIL () { return config.get<string>('admin.email') }
118   },
119   SIGNUP: {
120     get ENABLED () { return config.get<boolean>('signup.enabled') },
121     get LIMIT () { return config.get<number>('signup.limit') }
122   },
123   USER: {
124     get VIDEO_QUOTA () { return config.get<number>('user.video_quota') }
125   },
126   TRANSCODING: {
127     get ENABLED () { return config.get<boolean>('transcoding.enabled') },
128     get THREADS () { return config.get<number>('transcoding.threads') },
129     RESOLUTIONS: {
130       get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
131       get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') },
132       get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') },
133       get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') },
134       get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') }
135     }
136   },
137   CACHE: {
138     PREVIEWS: {
139       get SIZE () { return config.get<number>('cache.previews.size') }
140     }
141   }
142 }
143
144 // ---------------------------------------------------------------------------
145
146 const CONSTRAINTS_FIELDS = {
147   USERS: {
148     USERNAME: { min: 3, max: 20 }, // Length
149     PASSWORD: { min: 6, max: 255 }, // Length
150     VIDEO_QUOTA: { min: -1 }
151   },
152   VIDEO_ABUSES: {
153     REASON: { min: 2, max: 300 } // Length
154   },
155   VIDEO_CHANNELS: {
156     NAME: { min: 3, max: 120 }, // Length
157     DESCRIPTION: { min: 3, max: 250 }, // Length
158     URL: { min: 3, max: 2000 } // Length
159   },
160   VIDEOS: {
161     NAME: { min: 3, max: 120 }, // Length
162     TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length
163     DESCRIPTION: { min: 3, max: 3000 }, // Length
164     EXTNAME: [ '.mp4', '.ogv', '.webm' ],
165     INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
166     DURATION: { min: 1 }, // Number
167     TAGS: { min: 0, max: 5 }, // Number of total tags
168     TAG: { min: 2, max: 30 }, // Length
169     THUMBNAIL: { min: 2, max: 30 },
170     THUMBNAIL_DATA: { min: 0, max: 20000 }, // Bytes
171     VIEWS: { min: 0 },
172     LIKES: { min: 0 },
173     DISLIKES: { min: 0 },
174     FILE_SIZE: { min: 10 },
175     URL: { min: 3, max: 2000 } // Length
176   },
177   ACTORS: {
178     PUBLIC_KEY: { min: 10, max: 5000 }, // Length
179     PRIVATE_KEY: { min: 10, max: 5000 }, // Length
180     URL: { min: 3, max: 2000 }, // Length
181     AVATAR: {
182       EXTNAME: [ '.png', '.jpeg', '.jpg' ],
183       FILE_SIZE: {
184         max: 2 * 1024 * 1024 // 2MB
185       }
186     }
187   },
188   VIDEO_EVENTS: {
189     COUNT: { min: 0 }
190   },
191   VIDEO_COMMENTS: {
192     TEXT: { min: 2, max: 3000 }, // Length
193     URL: { min: 3, max: 2000 } // Length
194   }
195 }
196
197 const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = {
198   LIKE: 'like',
199   DISLIKE: 'dislike'
200 }
201
202 const VIDEO_CATEGORIES = {
203   1: 'Music',
204   2: 'Films',
205   3: 'Vehicles',
206   4: 'Art',
207   5: 'Sports',
208   6: 'Travels',
209   7: 'Gaming',
210   8: 'People',
211   9: 'Comedy',
212   10: 'Entertainment',
213   11: 'News',
214   12: 'How To',
215   13: 'Education',
216   14: 'Activism',
217   15: 'Science & Technology',
218   16: 'Animals',
219   17: 'Kids',
220   18: 'Food'
221 }
222
223 // See https://creativecommons.org/licenses/?lang=en
224 const VIDEO_LICENCES = {
225   1: 'Attribution',
226   2: 'Attribution - Share Alike',
227   3: 'Attribution - No Derivatives',
228   4: 'Attribution - Non Commercial',
229   5: 'Attribution - Non Commercial - Share Alike',
230   6: 'Attribution - Non Commercial - No Derivatives',
231   7: 'Public Domain Dedication'
232 }
233
234 // See https://en.wikipedia.org/wiki/List_of_languages_by_number_of_native_speakers#Nationalencyklopedin
235 const VIDEO_LANGUAGES = {
236   1: 'English',
237   2: 'Spanish',
238   3: 'Mandarin',
239   4: 'Hindi',
240   5: 'Arabic',
241   6: 'Portuguese',
242   7: 'Bengali',
243   8: 'Russian',
244   9: 'Japanese',
245   10: 'Punjabi',
246   11: 'German',
247   12: 'Korean',
248   13: 'French',
249   14: 'Italian'
250 }
251
252 const VIDEO_PRIVACIES = {
253   [VideoPrivacy.PUBLIC]: 'Public',
254   [VideoPrivacy.UNLISTED]: 'Unlisted',
255   [VideoPrivacy.PRIVATE]: 'Private'
256 }
257
258 const VIDEO_MIMETYPE_EXT = {
259   'video/webm': '.webm',
260   'video/ogg': '.ogv',
261   'video/mp4': '.mp4'
262 }
263
264 const AVATAR_MIMETYPE_EXT = {
265   'image/png': '.png',
266   'image/jpg': '.jpg',
267   'image/jpeg': '.jpg'
268 }
269
270 // ---------------------------------------------------------------------------
271
272 const SERVER_ACTOR_NAME = 'peertube'
273
274 const ACTIVITY_PUB = {
275   POTENTIAL_ACCEPT_HEADERS: [
276     'application/activity+json',
277     'application/ld+json',
278     'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
279   ],
280   ACCEPT_HEADER: 'application/activity+json, application/ld+json',
281   PUBLIC: 'https://www.w3.org/ns/activitystreams#Public',
282   COLLECTION_ITEMS_PER_PAGE: 10,
283   FETCH_PAGE_LIMIT: 100,
284   MAX_HTTP_ATTEMPT: 5,
285   URL_MIME_TYPES: {
286     VIDEO: Object.keys(VIDEO_MIMETYPE_EXT),
287     TORRENT: [ 'application/x-bittorrent' ],
288     MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
289   },
290   MAX_RECURSION_COMMENTS: 100,
291   ACTOR_REFRESH_INTERVAL: 3600 * 24 * 1000 // 1 day
292 }
293
294 const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
295   GROUP: 'Group',
296   PERSON: 'Person',
297   APPLICATION: 'Application'
298 }
299
300 // ---------------------------------------------------------------------------
301
302 const PRIVATE_RSA_KEY_SIZE = 2048
303
304 // Password encryption
305 const BCRYPT_SALT_SIZE = 10
306
307 // ---------------------------------------------------------------------------
308
309 // Express static paths (router)
310 const STATIC_PATHS = {
311   PREVIEWS: '/static/previews/',
312   THUMBNAILS: '/static/thumbnails/',
313   TORRENTS: '/static/torrents/',
314   WEBSEED: '/static/webseed/',
315   AVATARS: '/static/avatars/'
316 }
317
318 // Cache control
319 let STATIC_MAX_AGE = '30d'
320
321 // Videos thumbnail size
322 const THUMBNAILS_SIZE = {
323   width: 200,
324   height: 110
325 }
326 const PREVIEWS_SIZE = {
327   width: 560,
328   height: 315
329 }
330 const AVATARS_SIZE = {
331   width: 120,
332   height: 120
333 }
334
335 const EMBED_SIZE = {
336   width: 560,
337   height: 315
338 }
339
340 // Sub folders of cache directory
341 const CACHE = {
342   DIRECTORIES: {
343     PREVIEWS: join(CONFIG.STORAGE.CACHE_DIR, 'previews')
344   }
345 }
346
347 const ACCEPT_HEADERS = [ 'html', 'application/json' ].concat(ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS)
348
349 // ---------------------------------------------------------------------------
350
351 const OPENGRAPH_AND_OEMBED_COMMENT = '<!-- open graph and oembed tags -->'
352
353 // ---------------------------------------------------------------------------
354
355 // Special constants for a test instance
356 if (isTestInstance() === true) {
357   ACTOR_FOLLOW_SCORE.BASE = 20
358   JOBS_FETCHING_INTERVAL = 1000
359   REMOTE_SCHEME.HTTP = 'http'
360   REMOTE_SCHEME.WS = 'ws'
361   STATIC_MAX_AGE = '0'
362   ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE = 2
363   ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds
364   CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB
365   SCHEDULER_INTERVAL = 10000
366 }
367
368 updateWebserverConfig()
369
370 // ---------------------------------------------------------------------------
371
372 export {
373   API_VERSION,
374   AVATARS_SIZE,
375   ACCEPT_HEADERS,
376   BCRYPT_SALT_SIZE,
377   CACHE,
378   CONFIG,
379   CONSTRAINTS_FIELDS,
380   EMBED_SIZE,
381   JOB_STATES,
382   JOBS_FETCH_LIMIT_PER_CYCLE,
383   JOBS_FETCHING_INTERVAL,
384   JOB_CATEGORIES,
385   LAST_MIGRATION_VERSION,
386   OAUTH_LIFETIME,
387   OPENGRAPH_AND_OEMBED_COMMENT,
388   PAGINATION_COUNT_DEFAULT,
389   ACTOR_FOLLOW_SCORE,
390   PREVIEWS_SIZE,
391   REMOTE_SCHEME,
392   FOLLOW_STATES,
393   SERVER_ACTOR_NAME,
394   PRIVATE_RSA_KEY_SIZE,
395   SORTABLE_COLUMNS,
396   STATIC_MAX_AGE,
397   STATIC_PATHS,
398   ACTIVITY_PUB,
399   ACTIVITY_PUB_ACTOR_TYPES,
400   THUMBNAILS_SIZE,
401   VIDEO_CATEGORIES,
402   VIDEO_LANGUAGES,
403   VIDEO_PRIVACIES,
404   VIDEO_LICENCES,
405   VIDEO_RATE_TYPES,
406   VIDEO_MIMETYPE_EXT,
407   AVATAR_MIMETYPE_EXT,
408   SCHEDULER_INTERVAL
409 }
410
411 // ---------------------------------------------------------------------------
412
413 function getLocalConfigFilePath () {
414   const configSources = config.util.getConfigSources()
415   if (configSources.length === 0) throw new Error('Invalid config source.')
416
417   let filename = 'local'
418   if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
419   if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
420
421   return join(dirname(configSources[ 0 ].name), filename + '.json')
422 }
423
424 function updateWebserverConfig () {
425   CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT)
426   CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP)
427 }
428
429 export function reloadConfig () {
430
431   function directory () {
432     if (process.env.NODE_CONFIG_DIR) {
433       return process.env.NODE_CONFIG_DIR
434     }
435
436     return join(root(), 'config')
437   }
438
439   function purge () {
440     for (const fileName in require.cache) {
441       if (-1 === fileName.indexOf(directory())) {
442         continue
443       }
444
445       delete require.cache[fileName]
446     }
447
448     delete require.cache[require.resolve('config')]
449   }
450
451   purge()
452
453   config = require('config')
454
455   updateWebserverConfig()
456 }