Implement contact form on server side
[oweals/peertube.git] / server / controllers / api / config.ts
1 import * as express from 'express'
2 import { omit, snakeCase } from 'lodash'
3 import { ServerConfig, UserRight } from '../../../shared'
4 import { About } from '../../../shared/models/server/about.model'
5 import { CustomConfig } from '../../../shared/models/server/custom-config.model'
6 import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
7 import { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers'
8 import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
9 import { customConfigUpdateValidator } from '../../middlewares/validators/config'
10 import { ClientHtml } from '../../lib/client-html'
11 import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
12 import { remove, writeJSON } from 'fs-extra'
13 import { getServerCommit } from '../../helpers/utils'
14 import { Emailer } from '../../lib/emailer'
15 import { isNumeric } from 'validator'
16 import { objectConverter } from '../../helpers/core-utils'
17
18 const packageJSON = require('../../../../package.json')
19 const configRouter = express.Router()
20
21 const auditLogger = auditLoggerFactory('config')
22
23 configRouter.get('/about', getAbout)
24 configRouter.get('/',
25   asyncMiddleware(getConfig)
26 )
27
28 configRouter.get('/custom',
29   authenticate,
30   ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
31   asyncMiddleware(getCustomConfig)
32 )
33 configRouter.put('/custom',
34   authenticate,
35   ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
36   asyncMiddleware(customConfigUpdateValidator),
37   asyncMiddleware(updateCustomConfig)
38 )
39 configRouter.delete('/custom',
40   authenticate,
41   ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
42   asyncMiddleware(deleteCustomConfig)
43 )
44
45 let serverCommit: string
46 async function getConfig (req: express.Request, res: express.Response) {
47   const allowed = await isSignupAllowed()
48   const allowedForCurrentIP = isSignupAllowedForCurrentIP(req.ip)
49
50   if (serverCommit === undefined) serverCommit = await getServerCommit()
51
52   const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
53    .filter(key => CONFIG.TRANSCODING.ENABLED === CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
54    .map(r => parseInt(r, 10))
55
56   const json: ServerConfig = {
57     instance: {
58       name: CONFIG.INSTANCE.NAME,
59       shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
60       defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
61       defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
62       customizations: {
63         javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
64         css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
65       }
66     },
67     email: {
68       enabled: Emailer.Instance.isEnabled()
69     },
70     contactForm: {
71       enabled: CONFIG.CONTACT_FORM.ENABLED
72     },
73     serverVersion: packageJSON.version,
74     serverCommit,
75     signup: {
76       allowed,
77       allowedForCurrentIP,
78       requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
79     },
80     transcoding: {
81       enabledResolutions
82     },
83     import: {
84       videos: {
85         http: {
86           enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
87         },
88         torrent: {
89           enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
90         }
91       }
92     },
93     avatar: {
94       file: {
95         size: {
96           max: CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max
97         },
98         extensions: CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME
99       }
100     },
101     video: {
102       image: {
103         extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME,
104         size: {
105           max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max
106         }
107       },
108       file: {
109         extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
110       }
111     },
112     videoCaption: {
113       file: {
114         size: {
115           max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max
116         },
117         extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME
118       }
119     },
120     user: {
121       videoQuota: CONFIG.USER.VIDEO_QUOTA,
122       videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
123     }
124   }
125
126   return res.json(json)
127 }
128
129 function getAbout (req: express.Request, res: express.Response, next: express.NextFunction) {
130   const about: About = {
131     instance: {
132       name: CONFIG.INSTANCE.NAME,
133       shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
134       description: CONFIG.INSTANCE.DESCRIPTION,
135       terms: CONFIG.INSTANCE.TERMS
136     }
137   }
138
139   return res.json(about).end()
140 }
141
142 async function getCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
143   const data = customConfig()
144
145   return res.json(data).end()
146 }
147
148 async function deleteCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
149   await remove(CONFIG.CUSTOM_FILE)
150
151   auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig()))
152
153   reloadConfig()
154   ClientHtml.invalidCache()
155
156   const data = customConfig()
157
158   return res.json(data).end()
159 }
160
161 async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
162   const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
163
164   // camelCase to snake_case key + Force number conversion
165   const toUpdateJSON = convertCustomConfigBody(req.body)
166
167   await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
168
169   reloadConfig()
170   ClientHtml.invalidCache()
171
172   const data = customConfig()
173
174   auditLogger.update(
175     getAuditIdFromRes(res),
176     new CustomConfigAuditView(data),
177     oldCustomConfigAuditKeys
178   )
179
180   return res.json(data).end()
181 }
182
183 // ---------------------------------------------------------------------------
184
185 export {
186   configRouter
187 }
188
189 // ---------------------------------------------------------------------------
190
191 function customConfig (): CustomConfig {
192   return {
193     instance: {
194       name: CONFIG.INSTANCE.NAME,
195       shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
196       description: CONFIG.INSTANCE.DESCRIPTION,
197       terms: CONFIG.INSTANCE.TERMS,
198       defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
199       defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
200       customizations: {
201         css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
202         javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
203       }
204     },
205     services: {
206       twitter: {
207         username: CONFIG.SERVICES.TWITTER.USERNAME,
208         whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED
209       }
210     },
211     cache: {
212       previews: {
213         size: CONFIG.CACHE.PREVIEWS.SIZE
214       },
215       captions: {
216         size: CONFIG.CACHE.VIDEO_CAPTIONS.SIZE
217       }
218     },
219     signup: {
220       enabled: CONFIG.SIGNUP.ENABLED,
221       limit: CONFIG.SIGNUP.LIMIT,
222       requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
223     },
224     admin: {
225       email: CONFIG.ADMIN.EMAIL
226     },
227     contactForm: {
228       enabled: CONFIG.CONTACT_FORM.ENABLED
229     },
230     user: {
231       videoQuota: CONFIG.USER.VIDEO_QUOTA,
232       videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
233     },
234     transcoding: {
235       enabled: CONFIG.TRANSCODING.ENABLED,
236       allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
237       threads: CONFIG.TRANSCODING.THREADS,
238       resolutions: {
239         '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ],
240         '360p': CONFIG.TRANSCODING.RESOLUTIONS[ '360p' ],
241         '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ],
242         '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ],
243         '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ]
244       }
245     },
246     import: {
247       videos: {
248         http: {
249           enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
250         },
251         torrent: {
252           enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
253         }
254       }
255     }
256   }
257 }
258
259 function convertCustomConfigBody (body: CustomConfig) {
260   function keyConverter (k: string) {
261     // Transcoding resolutions exception
262     if (/^\d{3,4}p$/.exec(k)) return k
263
264     return snakeCase(k)
265   }
266
267   function valueConverter (v: any) {
268     if (isNumeric(v + '')) return parseInt('' + v, 10)
269
270     return v
271   }
272
273   return objectConverter(body, keyConverter, valueConverter)
274 }