site.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. const graphHelper = require('../../helpers/graph')
  2. const _ = require('lodash')
  3. const CleanCSS = require('clean-css')
  4. const path = require('path')
  5. /* global WIKI */
  6. module.exports = {
  7. Query: {
  8. async sites () {
  9. const sites = await WIKI.models.sites.query()
  10. return sites.map(s => ({
  11. ...s.config,
  12. id: s.id,
  13. hostname: s.hostname,
  14. isEnabled: s.isEnabled
  15. }))
  16. },
  17. async siteById (obj, args) {
  18. const site = await WIKI.models.sites.query().findById(args.id)
  19. return site ? {
  20. ...site.config,
  21. id: site.id,
  22. hostname: site.hostname,
  23. isEnabled: site.isEnabled
  24. } : null
  25. },
  26. async siteByHostname (obj, args) {
  27. let site = await WIKI.models.sites.query().where({
  28. hostname: args.hostname
  29. }).first()
  30. if (!site && !args.exact) {
  31. site = await WIKI.models.sites.query().where({
  32. hostname: '*'
  33. }).first()
  34. }
  35. return site ? {
  36. ...site.config,
  37. id: site.id,
  38. hostname: site.hostname,
  39. isEnabled: site.isEnabled
  40. } : null
  41. },
  42. // LEGACY
  43. async site() { return {} }
  44. },
  45. Mutation: {
  46. /**
  47. * CREATE SITE
  48. */
  49. async createSite (obj, args) {
  50. try {
  51. // -> Validate inputs
  52. if (!args.hostname || args.hostname.length < 1 || !/^(\\*)|([a-z0-9\-.:]+)$/.test(args.hostname)) {
  53. throw WIKI.ERROR(new Error('Invalid Site Hostname'), 'SiteCreateInvalidHostname')
  54. }
  55. if (!args.title || args.title.length < 1 || !/^[^<>"]+$/.test(args.title)) {
  56. throw WIKI.ERROR(new Error('Invalid Site Title'), 'SiteCreateInvalidTitle')
  57. }
  58. // -> Check for duplicate catch-all
  59. if (args.hostname === '*') {
  60. const site = await WIKI.models.sites.query().where({
  61. hostname: args.hostname
  62. }).first()
  63. if (site) {
  64. throw WIKI.ERROR(new Error('A site with a catch-all hostname already exists! Cannot have 2 catch-all hostnames.'), 'SiteCreateDuplicateCatchAll')
  65. }
  66. }
  67. // -> Create site
  68. const newSite = await WIKI.models.sites.createSite(args.hostname, {
  69. title: args.title
  70. })
  71. return {
  72. status: graphHelper.generateSuccess('Site created successfully'),
  73. site: newSite
  74. }
  75. } catch (err) {
  76. return graphHelper.generateError(err)
  77. }
  78. },
  79. /**
  80. * UPDATE SITE
  81. */
  82. async updateSite (obj, args) {
  83. try {
  84. // -> Load site
  85. const site = await WIKI.models.sites.query().findById(args.id)
  86. if (!site) {
  87. throw WIKI.ERROR(new Error('Invalid Site ID'), 'SiteInvalidId')
  88. }
  89. // -> Check for bad input
  90. if (_.has(args.patch, 'hostname') && _.trim(args.patch.hostname).length < 1) {
  91. throw WIKI.ERROR(new Error('Hostname is invalid.'), 'SiteInvalidHostname')
  92. }
  93. // -> Check for duplicate catch-all
  94. if (args.patch.hostname === '*' && site.hostname !== '*') {
  95. const dupSite = await WIKI.models.sites.query().where({ hostname: '*' }).first()
  96. if (dupSite) {
  97. throw WIKI.ERROR(new Error(`Site ${dupSite.config.title} with a catch-all hostname already exists! Cannot have 2 catch-all hostnames.`), 'SiteUpdateDuplicateCatchAll')
  98. }
  99. }
  100. // -> Format Code
  101. if (args.patch?.theme?.injectCSS) {
  102. args.patch.theme.injectCSS = new CleanCSS({ inline: false }).minify(args.patch.theme.injectCSS).styles
  103. }
  104. // -> Update site
  105. await WIKI.models.sites.updateSite(args.id, {
  106. hostname: args.patch.hostname ?? site.hostname,
  107. isEnabled: args.patch.isEnabled ?? site.isEnabled,
  108. config: _.defaultsDeep(_.omit(args.patch, ['hostname', 'isEnabled']), site.config)
  109. })
  110. return {
  111. status: graphHelper.generateSuccess('Site updated successfully')
  112. }
  113. } catch (err) {
  114. WIKI.logger.warn(err)
  115. return graphHelper.generateError(err)
  116. }
  117. },
  118. /**
  119. * DELETE SITE
  120. */
  121. async deleteSite (obj, args) {
  122. try {
  123. // -> Ensure site isn't last one
  124. const sitesCount = await WIKI.models.sites.query().count('id').first()
  125. if (sitesCount?.count && _.toNumber(sitesCount?.count) <= 1) {
  126. throw WIKI.ERROR(new Error('Cannot delete the last site. At least 1 site must exists at all times.'), 'SiteDeleteLastSite')
  127. }
  128. // -> Delete site
  129. await WIKI.models.sites.deleteSite(args.id)
  130. return {
  131. status: graphHelper.generateSuccess('Site deleted successfully')
  132. }
  133. } catch (err) {
  134. WIKI.logger.warn(err)
  135. return graphHelper.generateError(err)
  136. }
  137. },
  138. /**
  139. * UPLOAD LOGO
  140. */
  141. async uploadSiteLogo (obj, args) {
  142. try {
  143. const { filename, mimetype, createReadStream } = await args.image
  144. WIKI.logger.info(`Processing site logo ${filename} of type ${mimetype}...`)
  145. if (!WIKI.extensions.ext.sharp.isInstalled) {
  146. throw new Error('This feature requires the Sharp extension but it is not installed.')
  147. }
  148. console.info(mimetype)
  149. const destFormat = mimetype.startsWith('image/svg') ? 'svg' : 'png'
  150. const destPath = path.resolve(
  151. process.cwd(),
  152. WIKI.config.dataPath,
  153. `assets/logo.${destFormat}`
  154. )
  155. await WIKI.extensions.ext.sharp.resize({
  156. format: destFormat,
  157. inputStream: createReadStream(),
  158. outputPath: destPath,
  159. width: 100
  160. })
  161. WIKI.logger.info('New site logo processed successfully.')
  162. return {
  163. status: graphHelper.generateSuccess('Site logo uploaded successfully')
  164. }
  165. } catch (err) {
  166. return graphHelper.generateError(err)
  167. }
  168. },
  169. /**
  170. * UPLOAD FAVICON
  171. */
  172. async uploadSiteFavicon (obj, args) {
  173. const { filename, mimetype, createReadStream } = await args.image
  174. console.info(filename, mimetype)
  175. return {
  176. status: graphHelper.generateSuccess('Site favicon uploaded successfully')
  177. }
  178. },
  179. // LEGACY
  180. async site() { return {} }
  181. },
  182. SiteQuery: {
  183. async config(obj, args, context, info) {
  184. return {
  185. host: WIKI.config.host,
  186. title: WIKI.config.title,
  187. company: WIKI.config.company,
  188. contentLicense: WIKI.config.contentLicense,
  189. logoUrl: WIKI.config.logoUrl,
  190. ...WIKI.config.seo,
  191. ...WIKI.config.features,
  192. ...WIKI.config.security,
  193. authAutoLogin: WIKI.config.auth.autoLogin,
  194. authEnforce2FA: WIKI.config.auth.enforce2FA,
  195. authHideLocal: WIKI.config.auth.hideLocal,
  196. authLoginBgUrl: WIKI.config.auth.loginBgUrl,
  197. authJwtAudience: WIKI.config.auth.audience,
  198. authJwtExpiration: WIKI.config.auth.tokenExpiration,
  199. authJwtRenewablePeriod: WIKI.config.auth.tokenRenewal,
  200. uploadMaxFileSize: WIKI.config.uploads.maxFileSize,
  201. uploadMaxFiles: WIKI.config.uploads.maxFiles,
  202. uploadScanSVG: WIKI.config.uploads.scanSVG,
  203. uploadForceDownload: WIKI.config.uploads.forceDownload
  204. }
  205. }
  206. },
  207. SiteMutation: {
  208. async updateConfig(obj, args, context) {
  209. try {
  210. if (args.host) {
  211. let siteHost = _.trim(args.host)
  212. if (siteHost.endsWith('/')) {
  213. siteHost = siteHost.slice(0, -1)
  214. }
  215. WIKI.config.host = siteHost
  216. }
  217. if (args.title) {
  218. WIKI.config.title = _.trim(args.title)
  219. }
  220. if (args.company) {
  221. WIKI.config.company = _.trim(args.company)
  222. }
  223. if (args.contentLicense) {
  224. WIKI.config.contentLicense = args.contentLicense
  225. }
  226. if (args.logoUrl) {
  227. WIKI.config.logoUrl = _.trim(args.logoUrl)
  228. }
  229. WIKI.config.seo = {
  230. description: _.get(args, 'description', WIKI.config.seo.description),
  231. robots: _.get(args, 'robots', WIKI.config.seo.robots),
  232. analyticsService: _.get(args, 'analyticsService', WIKI.config.seo.analyticsService),
  233. analyticsId: _.get(args, 'analyticsId', WIKI.config.seo.analyticsId)
  234. }
  235. WIKI.config.auth = {
  236. autoLogin: _.get(args, 'authAutoLogin', WIKI.config.auth.autoLogin),
  237. enforce2FA: _.get(args, 'authEnforce2FA', WIKI.config.auth.enforce2FA),
  238. hideLocal: _.get(args, 'authHideLocal', WIKI.config.auth.hideLocal),
  239. loginBgUrl: _.get(args, 'authLoginBgUrl', WIKI.config.auth.loginBgUrl),
  240. audience: _.get(args, 'authJwtAudience', WIKI.config.auth.audience),
  241. tokenExpiration: _.get(args, 'authJwtExpiration', WIKI.config.auth.tokenExpiration),
  242. tokenRenewal: _.get(args, 'authJwtRenewablePeriod', WIKI.config.auth.tokenRenewal)
  243. }
  244. WIKI.config.features = {
  245. featurePageRatings: _.get(args, 'featurePageRatings', WIKI.config.features.featurePageRatings),
  246. featurePageComments: _.get(args, 'featurePageComments', WIKI.config.features.featurePageComments),
  247. featurePersonalWikis: _.get(args, 'featurePersonalWikis', WIKI.config.features.featurePersonalWikis)
  248. }
  249. WIKI.config.security = {
  250. securityOpenRedirect: _.get(args, 'securityOpenRedirect', WIKI.config.security.securityOpenRedirect),
  251. securityIframe: _.get(args, 'securityIframe', WIKI.config.security.securityIframe),
  252. securityReferrerPolicy: _.get(args, 'securityReferrerPolicy', WIKI.config.security.securityReferrerPolicy),
  253. securityTrustProxy: _.get(args, 'securityTrustProxy', WIKI.config.security.securityTrustProxy),
  254. securitySRI: _.get(args, 'securitySRI', WIKI.config.security.securitySRI),
  255. securityHSTS: _.get(args, 'securityHSTS', WIKI.config.security.securityHSTS),
  256. securityHSTSDuration: _.get(args, 'securityHSTSDuration', WIKI.config.security.securityHSTSDuration),
  257. securityCSP: _.get(args, 'securityCSP', WIKI.config.security.securityCSP),
  258. securityCSPDirectives: _.get(args, 'securityCSPDirectives', WIKI.config.security.securityCSPDirectives)
  259. }
  260. WIKI.config.uploads = {
  261. maxFileSize: _.get(args, 'uploadMaxFileSize', WIKI.config.uploads.maxFileSize),
  262. maxFiles: _.get(args, 'uploadMaxFiles', WIKI.config.uploads.maxFiles),
  263. scanSVG: _.get(args, 'uploadScanSVG', WIKI.config.uploads.scanSVG),
  264. forceDownload: _.get(args, 'uploadForceDownload', WIKI.config.uploads.forceDownload)
  265. }
  266. await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'auth', 'features', 'security', 'uploads'])
  267. if (WIKI.config.security.securityTrustProxy) {
  268. WIKI.app.enable('trust proxy')
  269. } else {
  270. WIKI.app.disable('trust proxy')
  271. }
  272. return {
  273. responseResult: graphHelper.generateSuccess('Site configuration updated successfully')
  274. }
  275. } catch (err) {
  276. return graphHelper.generateError(err)
  277. }
  278. }
  279. }
  280. }