]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/controllers/api/config.ts
Live streaming implementation first step
[github/Chocobozzz/PeerTube.git] / server / controllers / api / config.ts
CommitLineData
4a8d113b 1import { Hooks } from '@server/lib/plugins/hooks'
4d4e5cd4 2import * as express from 'express'
4a8d113b 3import { remove, writeJSON } from 'fs-extra'
09209296 4import { snakeCase } from 'lodash'
4a8d113b
C
5import validator from 'validator'
6import { RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig, UserRight } from '../../../shared'
09cababd
C
7import { About } from '../../../shared/models/server/about.model'
8import { CustomConfig } from '../../../shared/models/server/custom-config.model'
993cef4b 9import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
a4101923 10import { objectConverter } from '../../helpers/core-utils'
4a8d113b
C
11import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
12import { getServerCommit } from '../../helpers/utils'
4c1c1709 13import { CONFIG, isEmailEnabled, reloadConfig } from '../../initializers/config'
4a8d113b
C
14import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants'
15import { ClientHtml } from '../../lib/client-html'
18a6f04c 16import { PluginManager } from '../../lib/plugins/plugin-manager'
7cd4d2ba 17import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
4a8d113b
C
18import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
19import { customConfigUpdateValidator } from '../../middlewares/validators/config'
65fcc311
C
20
21const configRouter = express.Router()
22
80e36cd9
AB
23const auditLogger = auditLoggerFactory('config')
24
36f9424f 25configRouter.get('/about', getAbout)
eb080476
C
26configRouter.get('/',
27 asyncMiddleware(getConfig)
28)
36f9424f 29
fd206f0b
C
30configRouter.get('/custom',
31 authenticate,
32 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
a1587156 33 getCustomConfig
fd206f0b
C
34)
35configRouter.put('/custom',
36 authenticate,
37 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
a1587156 38 customConfigUpdateValidator,
fd206f0b
C
39 asyncMiddleware(updateCustomConfig)
40)
41configRouter.delete('/custom',
42 authenticate,
43 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
44 asyncMiddleware(deleteCustomConfig)
45)
65fcc311 46
abb2c792 47let serverCommit: string
66170ca8 48
1b5e2d72 49async function getConfig (req: express.Request, res: express.Response) {
4ce7eb71
C
50 const { allowed } = await Hooks.wrapPromiseFun(
51 isSignupAllowed,
70870adf
JL
52 {
53 ip: req.ip
54 },
4ce7eb71
C
55 'filter:api.user.signup.allowed.result'
56 )
57
ff2c1fe8 58 const allowedForCurrentIP = isSignupAllowedForCurrentIP(req.ip)
66170ca8 59 const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
1b5e2d72
C
60
61 if (serverCommit === undefined) serverCommit = await getServerCommit()
291e8d3e 62
eb080476 63 const json: ServerConfig = {
36f9424f 64 instance: {
00b5556c 65 name: CONFIG.INSTANCE.NAME,
2e3a0215 66 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
901637bb 67 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
f8802489 68 isNSFW: CONFIG.INSTANCE.IS_NSFW,
0883b324 69 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
00b5556c
C
70 customizations: {
71 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
72 css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
73 }
36f9424f 74 },
9677fca7
RK
75 search: {
76 remoteUri: {
77 users: CONFIG.SEARCH.REMOTE_URI.USERS,
78 anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
5fb2e288
C
79 },
80 searchIndex: {
81 enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED,
82 url: CONFIG.SEARCH.SEARCH_INDEX.URL,
83 disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH,
84 isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH
9677fca7
RK
85 }
86 },
7cd4d2ba 87 plugin: {
4a8d113b
C
88 registered: getRegisteredPlugins(),
89 registeredExternalAuths: getExternalAuthsPlugins(),
90 registeredIdAndPassAuths: getIdAndPassAuthPlugins()
7cd4d2ba
C
91 },
92 theme: {
66170ca8 93 registered: getRegisteredThemes(),
7cd4d2ba
C
94 default: defaultTheme
95 },
3b3b1820 96 email: {
4c1c1709 97 enabled: isEmailEnabled()
3b3b1820 98 },
a4101923
C
99 contactForm: {
100 enabled: CONFIG.CONTACT_FORM.ENABLED
101 },
66170ca8 102 serverVersion: PEERTUBE_VERSION,
abb2c792 103 serverCommit,
eb080476 104 signup: {
ff2c1fe8 105 allowed,
d9eaee39
JM
106 allowedForCurrentIP,
107 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
eb080476
C
108 },
109 transcoding: {
09209296
C
110 hls: {
111 enabled: CONFIG.TRANSCODING.HLS.ENABLED
112 },
d7a25329
C
113 webtorrent: {
114 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
115 },
c6c0fa6c
C
116 enabledResolutions: getEnabledResolutions('vod')
117 },
118 live: {
119 enabled: CONFIG.LIVE.ENABLED,
120
121 transcoding: {
122 enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
123 enabledResolutions: getEnabledResolutions('live')
124 }
01de67b9 125 },
5d08a6a7 126 import: {
b2977eec 127 videos: {
5d08a6a7
C
128 http: {
129 enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
a84b8fa5
C
130 },
131 torrent: {
132 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
5d08a6a7
C
133 }
134 }
135 },
7ccddd7b
JM
136 autoBlacklist: {
137 videos: {
138 ofUsers: {
139 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
140 }
141 }
142 },
01de67b9
C
143 avatar: {
144 file: {
145 size: {
146 max: CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max
147 },
148 extensions: CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME
149 }
150 },
151 video: {
6de36768
C
152 image: {
153 extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME,
154 size: {
155 max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max
156 }
157 },
01de67b9
C
158 file: {
159 extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
160 }
1869c875 161 },
40e87e9e
C
162 videoCaption: {
163 file: {
164 size: {
165 max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max
166 },
167 extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME
168 }
169 },
1869c875 170 user: {
bee0abff
FA
171 videoQuota: CONFIG.USER.VIDEO_QUOTA,
172 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
9b4b15f9
AB
173 },
174 trending: {
175 videos: {
176 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
177 }
31b6ddf8
C
178 },
179 tracker: {
180 enabled: CONFIG.TRACKER.ENABLED
000eb0e4
RK
181 },
182
183 followings: {
184 instance: {
185 autoFollowIndex: {
186 indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
187 }
188 }
72c33e71
C
189 },
190
191 broadcastMessage: {
192 enabled: CONFIG.BROADCAST_MESSAGE.ENABLED,
193 message: CONFIG.BROADCAST_MESSAGE.MESSAGE,
194 level: CONFIG.BROADCAST_MESSAGE.LEVEL,
195 dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE
65fcc311 196 }
eb080476 197 }
6a84aafd 198
eb080476 199 return res.json(json)
65fcc311
C
200}
201
f8802489 202function getAbout (req: express.Request, res: express.Response) {
36f9424f
C
203 const about: About = {
204 instance: {
205 name: CONFIG.INSTANCE.NAME,
2e3a0215 206 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
36f9424f 207 description: CONFIG.INSTANCE.DESCRIPTION,
ccc00cb2
C
208 terms: CONFIG.INSTANCE.TERMS,
209 codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
210
be04c6fd
C
211 hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
212
8ae03c37 213 creationReason: CONFIG.INSTANCE.CREATION_REASON,
ccc00cb2
C
214 moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
215 administrator: CONFIG.INSTANCE.ADMINISTRATOR,
216 maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
217 businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
218
219 languages: CONFIG.INSTANCE.LANGUAGES,
220 categories: CONFIG.INSTANCE.CATEGORIES
36f9424f
C
221 }
222 }
223
224 return res.json(about).end()
225}
226
a1587156 227function getCustomConfig (req: express.Request, res: express.Response) {
fd206f0b
C
228 const data = customConfig()
229
230 return res.json(data).end()
231}
232
f8802489 233async function deleteCustomConfig (req: express.Request, res: express.Response) {
62689b94 234 await remove(CONFIG.CUSTOM_FILE)
fd206f0b 235
993cef4b 236 auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig()))
80e36cd9 237
fd206f0b 238 reloadConfig()
e032aec9 239 ClientHtml.invalidCache()
fd206f0b
C
240
241 const data = customConfig()
242
c6c0fa6c 243 return res.json(data)
fd206f0b
C
244}
245
f8802489 246async function updateCustomConfig (req: express.Request, res: express.Response) {
80e36cd9 247 const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
fd206f0b 248
a4101923
C
249 // camelCase to snake_case key + Force number conversion
250 const toUpdateJSON = convertCustomConfigBody(req.body)
fd206f0b 251
62689b94 252 await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
fd206f0b
C
253
254 reloadConfig()
e032aec9 255 ClientHtml.invalidCache()
fd206f0b
C
256
257 const data = customConfig()
80e36cd9
AB
258
259 auditLogger.update(
993cef4b 260 getAuditIdFromRes(res),
80e36cd9
AB
261 new CustomConfigAuditView(data),
262 oldCustomConfigAuditKeys
263 )
264
c6c0fa6c 265 return res.json(data)
fd206f0b
C
266}
267
174e0855
RK
268function getRegisteredThemes () {
269 return PluginManager.Instance.getRegisteredThemes()
270 .map(t => ({
271 name: t.name,
272 version: t.version,
273 description: t.description,
274 css: t.css,
275 clientScripts: t.clientScripts
276 }))
277}
278
c6c0fa6c
C
279function getEnabledResolutions (type: 'vod' | 'live') {
280 const transcoding = type === 'vod'
281 ? CONFIG.TRANSCODING
282 : CONFIG.LIVE.TRANSCODING
283
284 return Object.keys(transcoding.RESOLUTIONS)
285 .filter(key => transcoding.ENABLED && transcoding.RESOLUTIONS[key] === true)
174e0855
RK
286 .map(r => parseInt(r, 10))
287}
288
289function getRegisteredPlugins () {
290 return PluginManager.Instance.getRegisteredPlugins()
291 .map(p => ({
292 name: p.name,
293 version: p.version,
294 description: p.description,
295 clientScripts: p.clientScripts
296 }))
297}
298
4a8d113b
C
299function getIdAndPassAuthPlugins () {
300 const result: RegisteredIdAndPassAuthConfig[] = []
301
302 for (const p of PluginManager.Instance.getIdAndPassAuths()) {
303 for (const auth of p.idAndPassAuths) {
304 result.push({
305 npmName: p.npmName,
ebefc902
C
306 name: p.name,
307 version: p.version,
4a8d113b
C
308 authName: auth.authName,
309 weight: auth.getWeight()
310 })
311 }
312 }
313
314 return result
315}
316
317function getExternalAuthsPlugins () {
318 const result: RegisteredExternalAuthConfig[] = []
319
320 for (const p of PluginManager.Instance.getExternalAuths()) {
321 for (const auth of p.externalAuths) {
322 result.push({
323 npmName: p.npmName,
ebefc902
C
324 name: p.name,
325 version: p.version,
4a8d113b 326 authName: auth.authName,
a5896799 327 authDisplayName: auth.authDisplayName()
4a8d113b
C
328 })
329 }
330 }
331
332 return result
333}
334
65fcc311
C
335// ---------------------------------------------------------------------------
336
337export {
174e0855
RK
338 configRouter,
339 getEnabledResolutions,
340 getRegisteredPlugins,
341 getRegisteredThemes
65fcc311 342}
fd206f0b
C
343
344// ---------------------------------------------------------------------------
345
346function customConfig (): CustomConfig {
347 return {
66b16caf
C
348 instance: {
349 name: CONFIG.INSTANCE.NAME,
2e3a0215 350 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
66b16caf 351 description: CONFIG.INSTANCE.DESCRIPTION,
00b5556c 352 terms: CONFIG.INSTANCE.TERMS,
ccc00cb2
C
353 codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
354
8ae03c37 355 creationReason: CONFIG.INSTANCE.CREATION_REASON,
ccc00cb2
C
356 moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
357 administrator: CONFIG.INSTANCE.ADMINISTRATOR,
358 maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
359 businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
be04c6fd 360 hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
ccc00cb2
C
361
362 languages: CONFIG.INSTANCE.LANGUAGES,
363 categories: CONFIG.INSTANCE.CATEGORIES,
364
f8802489 365 isNSFW: CONFIG.INSTANCE.IS_NSFW,
901637bb 366 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
0883b324 367 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
00b5556c
C
368 customizations: {
369 css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
370 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
371 }
66b16caf 372 },
7cd4d2ba
C
373 theme: {
374 default: CONFIG.THEME.DEFAULT
375 },
8be1afa1
C
376 services: {
377 twitter: {
378 username: CONFIG.SERVICES.TWITTER.USERNAME,
379 whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED
380 }
381 },
fd206f0b
C
382 cache: {
383 previews: {
384 size: CONFIG.CACHE.PREVIEWS.SIZE
40e87e9e
C
385 },
386 captions: {
387 size: CONFIG.CACHE.VIDEO_CAPTIONS.SIZE
fd206f0b
C
388 }
389 },
390 signup: {
391 enabled: CONFIG.SIGNUP.ENABLED,
d9eaee39
JM
392 limit: CONFIG.SIGNUP.LIMIT,
393 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
fd206f0b
C
394 },
395 admin: {
396 email: CONFIG.ADMIN.EMAIL
397 },
a4101923
C
398 contactForm: {
399 enabled: CONFIG.CONTACT_FORM.ENABLED
400 },
fd206f0b 401 user: {
bee0abff
FA
402 videoQuota: CONFIG.USER.VIDEO_QUOTA,
403 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
fd206f0b
C
404 },
405 transcoding: {
406 enabled: CONFIG.TRANSCODING.ENABLED,
14e2014a 407 allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
536598cf 408 allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
fd206f0b
C
409 threads: CONFIG.TRANSCODING.THREADS,
410 resolutions: {
a1587156
C
411 '0p': CONFIG.TRANSCODING.RESOLUTIONS['0p'],
412 '240p': CONFIG.TRANSCODING.RESOLUTIONS['240p'],
413 '360p': CONFIG.TRANSCODING.RESOLUTIONS['360p'],
414 '480p': CONFIG.TRANSCODING.RESOLUTIONS['480p'],
415 '720p': CONFIG.TRANSCODING.RESOLUTIONS['720p'],
416 '1080p': CONFIG.TRANSCODING.RESOLUTIONS['1080p'],
417 '2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p']
09209296 418 },
d7a25329
C
419 webtorrent: {
420 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
421 },
09209296
C
422 hls: {
423 enabled: CONFIG.TRANSCODING.HLS.ENABLED
fd206f0b 424 }
5d08a6a7 425 },
c6c0fa6c
C
426 live: {
427 enabled: CONFIG.LIVE.ENABLED,
428 transcoding: {
429 enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
430 threads: CONFIG.LIVE.TRANSCODING.THREADS,
431 resolutions: {
432 '240p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['240p'],
433 '360p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['360p'],
434 '480p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['480p'],
435 '720p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['720p'],
436 '1080p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1080p'],
437 '2160p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['2160p']
438 }
439 }
440 },
5d08a6a7
C
441 import: {
442 videos: {
443 http: {
444 enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
a84b8fa5
C
445 },
446 torrent: {
447 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
5d08a6a7
C
448 }
449 }
7ccddd7b
JM
450 },
451 autoBlacklist: {
452 videos: {
453 ofUsers: {
454 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
455 }
456 }
5b9c965d
C
457 },
458 followers: {
459 instance: {
14893eb7
C
460 enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED,
461 manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
5b9c965d 462 }
8424c402
C
463 },
464 followings: {
465 instance: {
466 autoFollowBack: {
467 enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED
468 },
469
470 autoFollowIndex: {
471 enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED,
472 indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
473 }
474 }
72c33e71
C
475 },
476 broadcastMessage: {
477 enabled: CONFIG.BROADCAST_MESSAGE.ENABLED,
478 message: CONFIG.BROADCAST_MESSAGE.MESSAGE,
479 level: CONFIG.BROADCAST_MESSAGE.LEVEL,
480 dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE
5fb2e288
C
481 },
482 search: {
483 remoteUri: {
484 users: CONFIG.SEARCH.REMOTE_URI.USERS,
485 anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
486 },
487 searchIndex: {
488 enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED,
489 url: CONFIG.SEARCH.SEARCH_INDEX.URL,
490 disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH,
491 isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH
492 }
3521ab8f 493 }
fd206f0b
C
494 }
495}
a4101923
C
496
497function convertCustomConfigBody (body: CustomConfig) {
498 function keyConverter (k: string) {
499 // Transcoding resolutions exception
500 if (/^\d{3,4}p$/.exec(k)) return k
3a149e9f 501 if (k === '0p') return k
a4101923
C
502
503 return snakeCase(k)
504 }
505
506 function valueConverter (v: any) {
7cde3b9c 507 if (validator.isNumeric(v + '')) return parseInt('' + v, 10)
a4101923
C
508
509 return v
510 }
511
512 return objectConverter(body, keyConverter, valueConverter)
513}