admin.js 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469
  1. module.exports = function(crowi, app) {
  2. 'use strict';
  3. const debug = require('debug')('growi:routes:admin')
  4. , logger = require('@alias/logger')('growi:routes:admin')
  5. , fs = require('fs')
  6. , models = crowi.models
  7. , Page = models.Page
  8. , PageGroupRelation = models.PageGroupRelation
  9. , User = models.User
  10. , ExternalAccount = models.ExternalAccount
  11. , UserGroup = models.UserGroup
  12. , UserGroupRelation = models.UserGroupRelation
  13. , Config = models.Config
  14. , GlobalNotificationSetting = models.GlobalNotificationSetting
  15. , GlobalNotificationMailSetting = models.GlobalNotificationMailSetting
  16. , GlobalNotificationSlackSetting = models.GlobalNotificationSlackSetting // eslint-disable-line no-unused-vars
  17. , PluginUtils = require('../plugins/plugin-utils')
  18. , pluginUtils = new PluginUtils()
  19. , ApiResponse = require('../util/apiResponse')
  20. , recommendedXssWhiteList = require('@commons/service/xss/recommendedXssWhiteList')
  21. , importer = require('../util/importer')(crowi)
  22. , MAX_PAGE_LIST = 50
  23. , actions = {};
  24. function createPager(total, limit, page, pagesCount, maxPageList) {
  25. const pager = {
  26. page: page,
  27. pagesCount: pagesCount,
  28. pages: [],
  29. total: total,
  30. previous: null,
  31. previousDots: false,
  32. next: null,
  33. nextDots: false,
  34. };
  35. if (page > 1) {
  36. pager.previous = page - 1;
  37. }
  38. if (page < pagesCount) {
  39. pager.next = page + 1;
  40. }
  41. let pagerMin = Math.max(1, Math.ceil(page - maxPageList/2));
  42. let pagerMax = Math.min(pagesCount, Math.floor(page + maxPageList/2));
  43. if (pagerMin === 1) {
  44. if (MAX_PAGE_LIST < pagesCount) {
  45. pagerMax = MAX_PAGE_LIST;
  46. }
  47. else {
  48. pagerMax = pagesCount;
  49. }
  50. }
  51. if (pagerMax === pagesCount) {
  52. if ((pagerMax - MAX_PAGE_LIST) < 1) {
  53. pagerMin = 1;
  54. }
  55. else {
  56. pagerMin = pagerMax - MAX_PAGE_LIST;
  57. }
  58. }
  59. pager.previousDots = null;
  60. if (pagerMin > 1) {
  61. pager.previousDots = true;
  62. }
  63. pager.nextDots = null;
  64. if (pagerMax < pagesCount) {
  65. pager.nextDots = true;
  66. }
  67. for (let i = pagerMin; i <= pagerMax; i++) {
  68. pager.pages.push(i);
  69. }
  70. return pager;
  71. }
  72. actions.index = function(req, res) {
  73. return res.render('admin/index', {
  74. plugins: pluginUtils.listPlugins(crowi.rootDir),
  75. });
  76. };
  77. // app.get('/admin/app' , admin.app.index);
  78. actions.app = {};
  79. actions.app.index = function(req, res) {
  80. var settingForm;
  81. settingForm = Config.setupCofigFormData('crowi', req.config);
  82. return res.render('admin/app', {
  83. settingForm: settingForm,
  84. });
  85. };
  86. actions.app.settingUpdate = function(req, res) {
  87. };
  88. // app.get('/admin/security' , admin.security.index);
  89. actions.security = {};
  90. actions.security.index = function(req, res) {
  91. const settingForm = Config.setupCofigFormData('crowi', req.config);
  92. return res.render('admin/security', { settingForm });
  93. };
  94. // app.get('/admin/markdown' , admin.markdown.index);
  95. actions.markdown = {};
  96. actions.markdown.index = function(req, res) {
  97. const config = crowi.getConfig();
  98. const markdownSetting = Config.setupCofigFormData('markdown', config);
  99. return res.render('admin/markdown', {
  100. markdownSetting: markdownSetting,
  101. recommendedXssWhiteList: recommendedXssWhiteList,
  102. });
  103. };
  104. // app.post('/admin/markdown/lineBreaksSetting' , admin.markdown.lineBreaksSetting);
  105. actions.markdown.lineBreaksSetting = function(req, res) {
  106. var markdownSetting = req.form.markdownSetting;
  107. req.session.markdownSetting = markdownSetting;
  108. if (req.form.isValid) {
  109. Config.updateNamespaceByArray('markdown', markdownSetting, function(err, config) {
  110. Config.updateConfigCache('markdown', config);
  111. req.session.markdownSetting = null;
  112. req.flash('successMessage', ['Successfully updated!']);
  113. return res.redirect('/admin/markdown');
  114. });
  115. }
  116. else {
  117. req.flash('errorMessage', req.form.errors);
  118. return res.redirect('/admin/markdown');
  119. }
  120. };
  121. // app.post('/admin/markdown/presentationSetting' , admin.markdown.presentationSetting);
  122. actions.markdown.presentationSetting = function(req, res) {
  123. let presentationSetting = req.form.markdownSetting;
  124. req.session.markdownSetting = presentationSetting;
  125. if (req.form.isValid) {
  126. Config.updateNamespaceByArray('markdown', presentationSetting, function(err, config) {
  127. Config.updateConfigCache('markdown', config);
  128. req.session.markdownSetting = null;
  129. req.flash('successMessage', ['Successfully updated!']);
  130. return res.redirect('/admin/markdown');
  131. });
  132. }
  133. else {
  134. req.flash('errorMessage', req.form.errors);
  135. return res.redirect('/admin/markdown');
  136. }
  137. };
  138. // app.post('/admin/markdown/xss-setting' , admin.markdown.xssSetting);
  139. actions.markdown.xssSetting = function(req, res) {
  140. let xssSetting = req.form.markdownSetting;
  141. xssSetting['markdown:xss:tagWhiteList'] = stringToArray(xssSetting['markdown:xss:tagWhiteList']);
  142. xssSetting['markdown:xss:attrWhiteList'] = stringToArray(xssSetting['markdown:xss:attrWhiteList']);
  143. req.session.markdownSetting = xssSetting;
  144. if (req.form.isValid) {
  145. Config.updateNamespaceByArray('markdown', xssSetting, function(err, config) {
  146. Config.updateConfigCache('markdown', config);
  147. req.session.xssSetting = null;
  148. req.flash('successMessage', ['Successfully updated!']);
  149. return res.redirect('/admin/markdown');
  150. });
  151. }
  152. else {
  153. req.flash('errorMessage', req.form.errors);
  154. return res.redirect('/admin/markdown');
  155. }
  156. };
  157. const stringToArray = (string) => {
  158. const array = string.split(',');
  159. return array.map(item => item.trim());
  160. };
  161. // app.get('/admin/customize' , admin.customize.index);
  162. actions.customize = {};
  163. actions.customize.index = function(req, res) {
  164. var settingForm;
  165. settingForm = Config.setupCofigFormData('crowi', req.config);
  166. const highlightJsCssSelectorOptions = {
  167. 'github': { name: '[Light] GitHub', border: false },
  168. 'github-gist': { name: '[Light] GitHub Gist', border: true },
  169. 'atom-one-light': { name: '[Light] Atom One Light', border: true },
  170. 'xcode': { name: '[Light] Xcode', border: true },
  171. 'vs': { name: '[Light] Vs', border: true },
  172. 'atom-one-dark': { name: '[Dark] Atom One Dark', border: false },
  173. 'hybrid': { name: '[Dark] Hybrid', border: false },
  174. 'monokai': { name: '[Dark] Monokai', border: false },
  175. 'tomorrow-night': { name: '[Dark] Tomorrow Night', border: false },
  176. 'vs2015': { name: '[Dark] Vs 2015', border: false },
  177. };
  178. return res.render('admin/customize', {
  179. settingForm: settingForm,
  180. highlightJsCssSelectorOptions: highlightJsCssSelectorOptions
  181. });
  182. };
  183. // app.get('/admin/notification' , admin.notification.index);
  184. actions.notification = {};
  185. actions.notification.index = async(req, res) => {
  186. const config = crowi.getConfig();
  187. const UpdatePost = crowi.model('UpdatePost');
  188. let slackSetting = Config.setupCofigFormData('notification', config);
  189. const hasSlackIwhUrl = Config.hasSlackIwhUrl(config);
  190. const hasSlackToken = Config.hasSlackToken(config);
  191. if (!Config.hasSlackIwhUrl(req.config)) {
  192. slackSetting['slack:incomingWebhookUrl'] = '';
  193. }
  194. if (req.session.slackSetting) {
  195. slackSetting = req.session.slackSetting;
  196. req.session.slackSetting = null;
  197. }
  198. const globalNotifications = await GlobalNotificationSetting.findAll();
  199. const userNotifications = await UpdatePost.findAll();
  200. return res.render('admin/notification', {
  201. userNotifications,
  202. slackSetting,
  203. hasSlackIwhUrl,
  204. hasSlackToken,
  205. globalNotifications,
  206. });
  207. };
  208. // app.post('/admin/notification/slackSetting' , admin.notification.slackauth);
  209. actions.notification.slackSetting = function(req, res) {
  210. var slackSetting = req.form.slackSetting;
  211. req.session.slackSetting = slackSetting;
  212. if (req.form.isValid) {
  213. Config.updateNamespaceByArray('notification', slackSetting, function(err, config) {
  214. Config.updateConfigCache('notification', config);
  215. req.flash('successMessage', ['Successfully Updated!']);
  216. req.session.slackSetting = null;
  217. // Re-setup
  218. crowi.setupSlack().then(function() {
  219. return res.redirect('/admin/notification');
  220. });
  221. });
  222. }
  223. else {
  224. req.flash('errorMessage', req.form.errors);
  225. return res.redirect('/admin/notification');
  226. }
  227. };
  228. // app.get('/admin/notification/slackAuth' , admin.notification.slackauth);
  229. actions.notification.slackAuth = function(req, res) {
  230. const code = req.query.code;
  231. const config = crowi.getConfig();
  232. if (!code || !Config.hasSlackConfig(req.config)) {
  233. return res.redirect('/admin/notification');
  234. }
  235. const slack = crowi.slack;
  236. slack.getOauthAccessToken(code)
  237. .then(data => {
  238. debug('oauth response', data);
  239. Config.updateNamespaceByArray('notification', {'slack:token': data.access_token}, function(err, config) {
  240. if (err) {
  241. req.flash('errorMessage', ['Failed to save access_token. Please try again.']);
  242. }
  243. else {
  244. Config.updateConfigCache('notification', config);
  245. req.flash('successMessage', ['Successfully Connected!']);
  246. }
  247. return res.redirect('/admin/notification');
  248. });
  249. }).catch(err => {
  250. debug('oauth response ERROR', err);
  251. req.flash('errorMessage', ['Failed to fetch access_token. Please do connect again.']);
  252. return res.redirect('/admin/notification');
  253. });
  254. };
  255. actions.search = {};
  256. actions.search.index = function(req, res) {
  257. return res.render('admin/search', {
  258. });
  259. };
  260. // app.post('/admin/notification/slackIwhSetting' , admin.notification.slackIwhSetting);
  261. actions.notification.slackIwhSetting = function(req, res) {
  262. var slackIwhSetting = req.form.slackIwhSetting;
  263. if (req.form.isValid) {
  264. Config.updateNamespaceByArray('notification', slackIwhSetting, function(err, config) {
  265. Config.updateConfigCache('notification', config);
  266. req.flash('successMessage', ['Successfully Updated!']);
  267. // Re-setup
  268. crowi.setupSlack().then(function() {
  269. return res.redirect('/admin/notification#slack-incoming-webhooks');
  270. });
  271. });
  272. }
  273. else {
  274. req.flash('errorMessage', req.form.errors);
  275. return res.redirect('/admin/notification#slack-incoming-webhooks');
  276. }
  277. };
  278. // app.post('/admin/notification/slackSetting/disconnect' , admin.notification.disconnectFromSlack);
  279. actions.notification.disconnectFromSlack = function(req, res) {
  280. const config = crowi.getConfig();
  281. const slack = crowi.slack;
  282. Config.updateNamespaceByArray('notification', {'slack:token': ''}, function(err, config) {
  283. Config.updateConfigCache('notification', config);
  284. req.flash('successMessage', ['Successfully Disconnected!']);
  285. return res.redirect('/admin/notification');
  286. });
  287. };
  288. actions.globalNotification = {};
  289. actions.globalNotification.detail = async(req, res) => {
  290. const notificationSettingId = req.params.id;
  291. let renderVars = {};
  292. if (notificationSettingId) {
  293. try {
  294. renderVars.setting = await GlobalNotificationSetting.findOne({_id: notificationSettingId});
  295. }
  296. catch (err) {
  297. logger.error(`Error in finding a global notification setting with {_id: ${notificationSettingId}}`);
  298. }
  299. }
  300. return res.render('admin/global-notification-detail', renderVars);
  301. };
  302. actions.globalNotification.create = (req, res) => {
  303. const form = req.form.notificationGlobal;
  304. let setting;
  305. switch (form.notifyToType) {
  306. case 'mail':
  307. setting = new GlobalNotificationMailSetting(crowi);
  308. setting.toEmail = form.toEmail;
  309. break;
  310. // case 'slack':
  311. // setting = new GlobalNotificationSlackSetting(crowi);
  312. // setting.slackChannels = form.slackChannels;
  313. // break;
  314. default:
  315. logger.error('GlobalNotificationSetting Type Error: undefined type');
  316. req.flash('errorMessage', 'Error occurred in creating a new global notification setting: undefined notification type');
  317. return res.redirect('/admin/notification#global-notification');
  318. }
  319. setting.triggerPath = form.triggerPath;
  320. setting.triggerEvents = getNotificationEvents(form);
  321. setting.save();
  322. return res.redirect('/admin/notification#global-notification');
  323. };
  324. actions.globalNotification.update = async(req, res) => {
  325. const form = req.form.notificationGlobal;
  326. const setting = await GlobalNotificationSetting.findOne({_id: form.id});
  327. switch (form.notifyToType) {
  328. case 'mail':
  329. setting.toEmail = form.toEmail;
  330. break;
  331. // case 'slack':
  332. // setting.slackChannels = form.slackChannels;
  333. // break;
  334. default:
  335. logger.error('GlobalNotificationSetting Type Error: undefined type');
  336. req.flash('errorMessage', 'Error occurred in updating the global notification setting: undefined notification type');
  337. return res.redirect('/admin/notification#global-notification');
  338. }
  339. setting.triggerPath = form.triggerPath;
  340. setting.triggerEvents = getNotificationEvents(form);
  341. setting.save();
  342. return res.redirect('/admin/notification#global-notification');
  343. };
  344. actions.globalNotification.remove = async(req, res) => {
  345. const id = req.params.id;
  346. try {
  347. await GlobalNotificationSetting.findOneAndRemove({_id: id});
  348. return res.redirect('/admin/notification#global-notification');
  349. }
  350. catch (err) {
  351. req.flash('errorMessage', 'Error in deleting global notification setting');
  352. return res.redirect('/admin/notification#global-notification');
  353. }
  354. };
  355. const getNotificationEvents = (form) => {
  356. let triggerEvents = [];
  357. const triggerEventKeys = Object.keys(form).filter(key => key.match(/^triggerEvent/));
  358. triggerEventKeys.forEach(key => {
  359. if (form[key]) {
  360. triggerEvents.push(form[key]);
  361. }
  362. });
  363. return triggerEvents;
  364. };
  365. actions.search.buildIndex = function(req, res) {
  366. var search = crowi.getSearcher();
  367. if (!search) {
  368. return res.redirect('/admin');
  369. }
  370. return new Promise(function(resolve, reject) {
  371. search.deleteIndex()
  372. .then(function(data) {
  373. debug('Index deleted.');
  374. resolve();
  375. }).catch(function(err) {
  376. debug('Delete index Error, but if it is initialize, its ok.', err);
  377. resolve();
  378. });
  379. })
  380. .then(function() {
  381. return search.buildIndex();
  382. })
  383. .then(function(data) {
  384. if (!data.errors) {
  385. debug('Index created.');
  386. }
  387. return search.addAllPages();
  388. })
  389. .then(function(data) {
  390. if (!data.errors) {
  391. debug('Data is successfully indexed.');
  392. req.flash('successMessage', 'Data is successfully indexed.');
  393. }
  394. else {
  395. debug('Data index error.', data.errors);
  396. req.flash('errorMessage', `Data index error: ${data.errors}`);
  397. }
  398. return res.redirect('/admin/search');
  399. })
  400. .catch(function(err) {
  401. debug('Error', err);
  402. req.flash('errorMessage', `Error: ${err}`);
  403. return res.redirect('/admin/search');
  404. });
  405. };
  406. actions.user = {};
  407. actions.user.index = function(req, res) {
  408. var page = parseInt(req.query.page) || 1;
  409. User.findUsersWithPagination({page: page}, function(err, result) {
  410. const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
  411. return res.render('admin/users', {
  412. users: result.docs,
  413. pager: pager
  414. });
  415. });
  416. };
  417. actions.user.invite = function(req, res) {
  418. var form = req.form.inviteForm;
  419. var toSendEmail = form.sendEmail || false;
  420. if (req.form.isValid) {
  421. User.createUsersByInvitation(form.emailList.split('\n'), toSendEmail, function(err, userList) {
  422. if (err) {
  423. req.flash('errorMessage', req.form.errors.join('\n'));
  424. }
  425. else {
  426. req.flash('createdUser', userList);
  427. }
  428. return res.redirect('/admin/users');
  429. });
  430. }
  431. else {
  432. req.flash('errorMessage', req.form.errors.join('\n'));
  433. return res.redirect('/admin/users');
  434. }
  435. };
  436. actions.user.makeAdmin = function(req, res) {
  437. var id = req.params.id;
  438. User.findById(id, function(err, userData) {
  439. userData.makeAdmin(function(err, userData) {
  440. if (err === null) {
  441. req.flash('successMessage', userData.name + 'さんのアカウントを管理者に設定しました。');
  442. }
  443. else {
  444. req.flash('errorMessage', '更新に失敗しました。');
  445. debug(err, userData);
  446. }
  447. return res.redirect('/admin/users');
  448. });
  449. });
  450. };
  451. actions.user.removeFromAdmin = function(req, res) {
  452. var id = req.params.id;
  453. User.findById(id, function(err, userData) {
  454. userData.removeFromAdmin(function(err, userData) {
  455. if (err === null) {
  456. req.flash('successMessage', userData.name + 'さんのアカウントを管理者から外しました。');
  457. }
  458. else {
  459. req.flash('errorMessage', '更新に失敗しました。');
  460. debug(err, userData);
  461. }
  462. return res.redirect('/admin/users');
  463. });
  464. });
  465. };
  466. actions.user.activate = function(req, res) {
  467. var id = req.params.id;
  468. User.findById(id, function(err, userData) {
  469. userData.statusActivate(function(err, userData) {
  470. if (err === null) {
  471. req.flash('successMessage', userData.name + 'さんのアカウントを有効化しました');
  472. }
  473. else {
  474. req.flash('errorMessage', '更新に失敗しました。');
  475. debug(err, userData);
  476. }
  477. return res.redirect('/admin/users');
  478. });
  479. });
  480. };
  481. actions.user.suspend = function(req, res) {
  482. var id = req.params.id;
  483. User.findById(id, function(err, userData) {
  484. userData.statusSuspend(function(err, userData) {
  485. if (err === null) {
  486. req.flash('successMessage', userData.name + 'さんのアカウントを利用停止にしました');
  487. }
  488. else {
  489. req.flash('errorMessage', '更新に失敗しました。');
  490. debug(err, userData);
  491. }
  492. return res.redirect('/admin/users');
  493. });
  494. });
  495. };
  496. actions.user.remove = function(req, res) {
  497. const id = req.params.id;
  498. let username = '';
  499. return new Promise((resolve, reject) => {
  500. User.findById(id, (err, userData) => {
  501. username = userData.username;
  502. return resolve(userData);
  503. });
  504. })
  505. .then((userData) => {
  506. return new Promise((resolve, reject) => {
  507. userData.statusDelete((err, userData) => {
  508. if (err) {
  509. reject(err);
  510. }
  511. resolve(userData);
  512. });
  513. });
  514. })
  515. .then((userData) => {
  516. // remove all External Accounts
  517. return ExternalAccount.remove({user: userData}).then(() => userData);
  518. })
  519. .then((userData) => {
  520. return Page.removePageByPath(`/user/${username}`).then(() => userData);
  521. })
  522. .then((userData) => {
  523. req.flash('successMessage', `${username} さんのアカウントを削除しました`);
  524. return res.redirect('/admin/users');
  525. })
  526. .catch((err) => {
  527. req.flash('errorMessage', '削除に失敗しました。');
  528. return res.redirect('/admin/users');
  529. });
  530. };
  531. // これやったときの relation の挙動未確認
  532. actions.user.removeCompletely = function(req, res) {
  533. // ユーザーの物理削除
  534. var id = req.params.id;
  535. User.removeCompletelyById(id, function(err, removed) {
  536. if (err) {
  537. debug('Error while removing user.', err, id);
  538. req.flash('errorMessage', '完全な削除に失敗しました。');
  539. }
  540. else {
  541. req.flash('successMessage', '削除しました');
  542. }
  543. return res.redirect('/admin/users');
  544. });
  545. };
  546. // app.post('/_api/admin/users.resetPassword' , admin.api.usersResetPassword);
  547. actions.user.resetPassword = function(req, res) {
  548. const id = req.body.user_id;
  549. const User = crowi.model('User');
  550. User.resetPasswordByRandomString(id)
  551. .then(function(data) {
  552. data.user = User.filterToPublicFields(data.user);
  553. return res.json(ApiResponse.success(data));
  554. }).catch(function(err) {
  555. debug('Error on reseting password', err);
  556. return res.json(ApiResponse.error('Error'));
  557. });
  558. };
  559. actions.externalAccount = {};
  560. actions.externalAccount.index = function(req, res) {
  561. const page = parseInt(req.query.page) || 1;
  562. ExternalAccount.findAllWithPagination({page})
  563. .then((result) => {
  564. const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
  565. return res.render('admin/external-accounts', {
  566. accounts: result.docs,
  567. pager: pager
  568. });
  569. });
  570. };
  571. actions.externalAccount.remove = function(req, res) {
  572. const accountId = req.params.id;
  573. ExternalAccount.findOneAndRemove({accountId})
  574. .then((result) => {
  575. if (result == null) {
  576. req.flash('errorMessage', '削除に失敗しました。');
  577. return res.redirect('/admin/users/external-accounts');
  578. }
  579. else {
  580. req.flash('successMessage', `外部アカウント '${accountId}' を削除しました`);
  581. return res.redirect('/admin/users/external-accounts');
  582. }
  583. });
  584. };
  585. actions.userGroup = {};
  586. actions.userGroup.index = function(req, res) {
  587. var page = parseInt(req.query.page) || 1;
  588. var renderVar = {
  589. userGroups: [],
  590. userGroupRelations: new Map(),
  591. pager: null,
  592. };
  593. UserGroup.findUserGroupsWithPagination({ page: page })
  594. .then((result) => {
  595. const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
  596. var userGroups = result.docs;
  597. renderVar.userGroups = userGroups;
  598. renderVar.pager = pager;
  599. return userGroups.map((userGroup) => {
  600. return new Promise((resolve, reject) => {
  601. UserGroupRelation.findAllRelationForUserGroup(userGroup)
  602. .then((relations) => {
  603. return resolve([userGroup, relations]);
  604. });
  605. });
  606. });
  607. })
  608. .then((allRelationsPromise) => {
  609. return Promise.all(allRelationsPromise);
  610. })
  611. .then((relations) => {
  612. renderVar.userGroupRelations = new Map(relations);
  613. debug('in findUserGroupsWithPagination findAllRelationForUserGroupResult', renderVar.userGroupRelations);
  614. return res.render('admin/user-groups', renderVar);
  615. })
  616. .catch( function(err) {
  617. debug('Error on find all relations', err);
  618. return res.json(ApiResponse.error('Error'));
  619. });
  620. };
  621. // グループ詳細
  622. actions.userGroup.detail = function(req, res) {
  623. const userGroupId = req.params.id;
  624. const renderVar = {
  625. userGroup: null,
  626. userGroupRelations: [],
  627. pageGroupRelations: [],
  628. notRelatedusers: []
  629. };
  630. let targetUserGroup = null;
  631. UserGroup.findOne({ _id: userGroupId})
  632. .then(function(userGroup) {
  633. targetUserGroup = userGroup;
  634. if (targetUserGroup == null) {
  635. req.flash('errorMessage', 'グループがありません');
  636. throw new Error('no userGroup is exists. ', name);
  637. }
  638. else {
  639. renderVar.userGroup = targetUserGroup;
  640. return Promise.all([
  641. // get all user and group relations
  642. UserGroupRelation.findAllRelationForUserGroup(targetUserGroup),
  643. // get all page and group relations
  644. PageGroupRelation.findAllRelationForUserGroup(targetUserGroup),
  645. // get all not related users for group
  646. UserGroupRelation.findUserByNotRelatedGroup(targetUserGroup),
  647. ]);
  648. }
  649. })
  650. .then((resolves) => {
  651. renderVar.userGroupRelations = resolves[0];
  652. renderVar.pageGroupRelations = resolves[1];
  653. renderVar.notRelatedusers = resolves[2];
  654. debug('notRelatedusers', renderVar.notRelatedusers);
  655. return res.render('admin/user-group-detail', renderVar);
  656. })
  657. .catch((err) => {
  658. req.flash('errorMessage', 'ユーザグループの検索に失敗しました');
  659. debug('Error on get userGroupDetail', err);
  660. return res.redirect('/admin/user-groups');
  661. });
  662. };
  663. //グループの生成
  664. actions.userGroup.create = function(req, res) {
  665. const form = req.form.createGroupForm;
  666. if (req.form.isValid) {
  667. const userGroupName = crowi.xss.process(form.userGroupName);
  668. UserGroup.createGroupByName(userGroupName)
  669. .then((newUserGroup) => {
  670. req.flash('successMessage', newUserGroup.name);
  671. req.flash('createdUserGroup', newUserGroup);
  672. return res.redirect('/admin/user-groups');
  673. })
  674. .catch((err) => {
  675. debug('create userGroup error:', err);
  676. req.flash('errorMessage', '同じグループ名が既に存在します。');
  677. });
  678. }
  679. else {
  680. req.flash('errorMessage', req.form.errors.join('\n'));
  681. return res.redirect('/admin/user-groups');
  682. }
  683. };
  684. //
  685. actions.userGroup.update = function(req, res) {
  686. const userGroupId = req.params.userGroupId;
  687. const name = crowi.xss.process(req.body.name);
  688. UserGroup.findById(userGroupId)
  689. .then((userGroupData) => {
  690. if (userGroupData == null) {
  691. req.flash('errorMessage', 'グループの検索に失敗しました。');
  692. return new Promise();
  693. }
  694. else {
  695. // 名前存在チェック
  696. return UserGroup.isRegisterableName(name)
  697. .then((isRegisterableName) => {
  698. // 既に存在するグループ名に更新しようとした場合はエラー
  699. if (!isRegisterableName) {
  700. req.flash('errorMessage', 'グループ名が既に存在します。');
  701. }
  702. else {
  703. return userGroupData.updateName(name)
  704. .then(() => {
  705. req.flash('successMessage', 'グループ名を更新しました。');
  706. })
  707. .catch((err) => {
  708. req.flash('errorMessage', 'グループ名の更新に失敗しました。');
  709. });
  710. }
  711. });
  712. }
  713. })
  714. .then(() => {
  715. return res.redirect('/admin/user-group-detail/' + userGroupId);
  716. });
  717. };
  718. actions.userGroup.uploadGroupPicture = function(req, res) {
  719. var fileUploader = require('../service/file-uploader')(crowi, app);
  720. //var storagePlugin = new pluginService('storage');
  721. //var storage = require('../service/storage').StorageService(config);
  722. var userGroupId = req.params.userGroupId;
  723. var tmpFile = req.file || null;
  724. if (!tmpFile) {
  725. return res.json({
  726. 'status': false,
  727. 'message': 'File type error.'
  728. });
  729. }
  730. UserGroup.findById(userGroupId, function(err, userGroupData) {
  731. if (!userGroupData) {
  732. return res.json({
  733. 'status': false,
  734. 'message': 'UserGroup error.'
  735. });
  736. }
  737. var tmpPath = tmpFile.path;
  738. var filePath = UserGroup.createUserGroupPictureFilePath(userGroupData, tmpFile.filename + tmpFile.originalname);
  739. var acceptableFileType = /image\/.+/;
  740. if (!tmpFile.mimetype.match(acceptableFileType)) {
  741. return res.json({
  742. 'status': false,
  743. 'message': 'File type error. Only image files is allowed to set as user picture.',
  744. });
  745. }
  746. var tmpFileStream = fs.createReadStream(tmpPath, { flags: 'r', encoding: null, fd: null, mode: '0666', autoClose: true });
  747. fileUploader.uploadFile(filePath, tmpFile.mimetype, tmpFileStream, {})
  748. .then(function(data) {
  749. var imageUrl = fileUploader.generateUrl(filePath);
  750. userGroupData.updateImage(imageUrl)
  751. .then(() => {
  752. fs.unlink(tmpPath, function(err) {
  753. if (err) {
  754. debug('Error while deleting tmp file.', err);
  755. }
  756. return res.json({
  757. 'status': true,
  758. 'url': imageUrl,
  759. 'message': '',
  760. });
  761. });
  762. });
  763. }).catch(function(err) {
  764. debug('Uploading error', err);
  765. return res.json({
  766. 'status': false,
  767. 'message': 'Error while uploading to ',
  768. });
  769. });
  770. });
  771. };
  772. actions.userGroup.deletePicture = function(req, res) {
  773. const userGroupId = req.params.userGroupId;
  774. let userGroupName = null;
  775. UserGroup.findById(userGroupId)
  776. .then((userGroupData) => {
  777. if (userGroupData == null) {
  778. return Promise.reject();
  779. }
  780. else {
  781. userGroupName = userGroupData.name;
  782. return userGroupData.deleteImage();
  783. }
  784. })
  785. .then((updated) => {
  786. req.flash('successMessage', 'Deleted group picture');
  787. return res.redirect('/admin/user-group-detail/' + userGroupId);
  788. })
  789. .catch((err) => {
  790. debug('An error occured.', err);
  791. req.flash('errorMessage', 'Error while deleting group picture');
  792. if (userGroupName == null) {
  793. return res.redirect('/admin/user-groups/');
  794. }
  795. else {
  796. return res.redirect('/admin/user-group-detail/' + userGroupId);
  797. }
  798. });
  799. };
  800. // app.post('/_api/admin/user-group/delete' , admin.userGroup.removeCompletely);
  801. actions.userGroup.removeCompletely = function(req, res) {
  802. const id = req.body.user_group_id;
  803. const fileUploader = require('../service/file-uploader')(crowi, app);
  804. UserGroup.removeCompletelyById(id)
  805. //// TODO remove attachments
  806. // couldn't remove because filePath includes '/uploads/uploads'
  807. // Error: ENOENT: no such file or directory, unlink 'C:\dev\growi\public\uploads\uploads\userGroup\5b1df18ab69611651cc71495.png
  808. //
  809. // .then(removed => {
  810. // if (removed.image != null) {
  811. // fileUploader.deleteFile(null, removed.image);
  812. // }
  813. // })
  814. .then(() => {
  815. req.flash('successMessage', '削除しました');
  816. return res.redirect('/admin/user-groups');
  817. })
  818. .catch((err) => {
  819. debug('Error while removing userGroup.', err, id);
  820. req.flash('errorMessage', '完全な削除に失敗しました。');
  821. return res.redirect('/admin/user-groups');
  822. });
  823. };
  824. actions.userGroupRelation = {};
  825. actions.userGroupRelation.index = function(req, res) {
  826. };
  827. actions.userGroupRelation.create = function(req, res) {
  828. const User = crowi.model('User');
  829. const UserGroup = crowi.model('UserGroup');
  830. const UserGroupRelation = crowi.model('UserGroupRelation');
  831. // req params
  832. const userName = req.body.user_name;
  833. const userGroupId = req.body.user_group_id;
  834. let user = null;
  835. let userGroup = null;
  836. Promise.all([
  837. // ユーザグループをIDで検索
  838. UserGroup.findById(userGroupId),
  839. // ユーザを名前で検索
  840. User.findUserByUsername(userName),
  841. ])
  842. .then((resolves) => {
  843. userGroup = resolves[0];
  844. user = resolves[1];
  845. // Relation を作成
  846. UserGroupRelation.createRelation(userGroup, user);
  847. })
  848. .then((result) => {
  849. return res.redirect('/admin/user-group-detail/' + userGroup.id);
  850. }).catch((err) => {
  851. debug('Error on create user-group relation', err);
  852. req.flash('errorMessage', 'Error on create user-group relation');
  853. return res.redirect('/admin/user-group-detail/' + userGroup.id);
  854. });
  855. };
  856. actions.userGroupRelation.remove = function(req, res) {
  857. const UserGroupRelation = crowi.model('UserGroupRelation');
  858. const userGroupId = req.params.id;
  859. const relationId = req.params.relationId;
  860. UserGroupRelation.removeById(relationId)
  861. .then(() =>{
  862. return res.redirect('/admin/user-group-detail/' + userGroupId);
  863. })
  864. .catch((err) => {
  865. debug('Error on remove user-group-relation', err);
  866. req.flash('errorMessage', 'グループのユーザ削除に失敗しました。');
  867. });
  868. };
  869. // Importer management
  870. actions.importer = {};
  871. actions.importer.index = function(req, res) {
  872. var settingForm;
  873. settingForm = Config.setupCofigFormData('crowi', req.config);
  874. return res.render('admin/importer', {
  875. settingForm: settingForm,
  876. });
  877. };
  878. actions.api = {};
  879. actions.api.appSetting = function(req, res) {
  880. var form = req.form.settingForm;
  881. if (req.form.isValid) {
  882. debug('form content', form);
  883. // mail setting ならここで validation
  884. if (form['mail:from']) {
  885. validateMailSetting(req, form, function(err, data) {
  886. debug('Error validate mail setting: ', err, data);
  887. if (err) {
  888. req.form.errors.push('SMTPを利用したテストメール送信に失敗しました。設定をみなおしてください。');
  889. return res.json({status: false, message: req.form.errors.join('\n')});
  890. }
  891. return saveSetting(req, res, form);
  892. });
  893. }
  894. else {
  895. return saveSetting(req, res, form);
  896. }
  897. }
  898. else {
  899. return res.json({status: false, message: req.form.errors.join('\n')});
  900. }
  901. };
  902. actions.api.securitySetting = function(req, res) {
  903. const form = req.form.settingForm;
  904. if (req.form.isValid) {
  905. debug('form content', form);
  906. return saveSetting(req, res, form);
  907. }
  908. else {
  909. return res.json({status: false, message: req.form.errors.join('\n')});
  910. }
  911. };
  912. actions.api.securityPassportLdapSetting = function(req, res) {
  913. var form = req.form.settingForm;
  914. if (!req.form.isValid) {
  915. return res.json({status: false, message: req.form.errors.join('\n')});
  916. }
  917. debug('form content', form);
  918. return saveSettingAsync(form)
  919. .then(() => {
  920. const config = crowi.getConfig();
  921. // reset strategy
  922. crowi.passportService.resetLdapStrategy();
  923. // setup strategy
  924. if (Config.isEnabledPassportLdap(config)) {
  925. crowi.passportService.setupLdapStrategy(true);
  926. }
  927. return;
  928. })
  929. .then(() => {
  930. res.json({status: true});
  931. });
  932. };
  933. actions.api.securityPassportSamlSetting = async(req, res) => {
  934. const form = req.form.settingForm;
  935. if (!req.form.isValid) {
  936. return res.json({status: false, message: req.form.errors.join('\n')});
  937. }
  938. debug('form content', form);
  939. await saveSettingAsync(form);
  940. const config = await crowi.getConfig();
  941. // reset strategy
  942. await crowi.passportService.resetSamlStrategy();
  943. // setup strategy
  944. if (Config.isEnabledPassportSaml(config)) {
  945. try {
  946. await crowi.passportService.setupSamlStrategy(true);
  947. }
  948. catch (err) {
  949. // reset
  950. await crowi.passportService.resetSamlStrategy();
  951. return res.json({status: false, message: err.message});
  952. }
  953. }
  954. return res.json({status: true});
  955. };
  956. actions.api.securityPassportGoogleSetting = async(req, res) => {
  957. const form = req.form.settingForm;
  958. if (!req.form.isValid) {
  959. return res.json({status: false, message: req.form.errors.join('\n')});
  960. }
  961. debug('form content', form);
  962. await saveSettingAsync(form);
  963. const config = await crowi.getConfig();
  964. // reset strategy
  965. await crowi.passportService.resetGoogleStrategy();
  966. // setup strategy
  967. if (Config.isEnabledPassportGoogle(config)) {
  968. try {
  969. await crowi.passportService.setupGoogleStrategy(true);
  970. }
  971. catch (err) {
  972. // reset
  973. await crowi.passportService.resetGoogleStrategy();
  974. return res.json({status: false, message: err.message});
  975. }
  976. }
  977. return res.json({status: true});
  978. };
  979. actions.api.securityPassportGitHubSetting = async(req, res) => {
  980. const form = req.form.settingForm;
  981. if (!req.form.isValid) {
  982. return res.json({status: false, message: req.form.errors.join('\n')});
  983. }
  984. debug('form content', form);
  985. await saveSettingAsync(form);
  986. const config = await crowi.getConfig();
  987. // reset strategy
  988. await crowi.passportService.resetGitHubStrategy();
  989. // setup strategy
  990. if (Config.isEnabledPassportGitHub(config)) {
  991. try {
  992. await crowi.passportService.setupGitHubStrategy(true);
  993. }
  994. catch (err) {
  995. // reset
  996. await crowi.passportService.resetGitHubStrategy();
  997. return res.json({status: false, message: err.message});
  998. }
  999. }
  1000. return res.json({status: true});
  1001. };
  1002. actions.api.securityPassportTwitterSetting = async(req, res) => {
  1003. const form = req.form.settingForm;
  1004. if (!req.form.isValid) {
  1005. return res.json({status: false, message: req.form.errors.join('\n')});
  1006. }
  1007. debug('form content', form);
  1008. await saveSettingAsync(form);
  1009. const config = await crowi.getConfig();
  1010. // reset strategy
  1011. await crowi.passportService.resetTwitterStrategy();
  1012. // setup strategy
  1013. if (Config.isEnabledPassportTwitter(config)) {
  1014. try {
  1015. await crowi.passportService.setupTwitterStrategy(true);
  1016. }
  1017. catch (err) {
  1018. // reset
  1019. await crowi.passportService.resetTwitterStrategy();
  1020. return res.json({status: false, message: err.message});
  1021. }
  1022. }
  1023. return res.json({status: true});
  1024. };
  1025. actions.api.customizeSetting = function(req, res) {
  1026. const form = req.form.settingForm;
  1027. if (req.form.isValid) {
  1028. debug('form content', form);
  1029. return saveSetting(req, res, form);
  1030. }
  1031. else {
  1032. return res.json({status: false, message: req.form.errors.join('\n')});
  1033. }
  1034. };
  1035. actions.api.customizeSetting = function(req, res) {
  1036. const form = req.form.settingForm;
  1037. if (req.form.isValid) {
  1038. debug('form content', form);
  1039. return saveSetting(req, res, form);
  1040. }
  1041. else {
  1042. return res.json({status: false, message: req.form.errors.join('\n')});
  1043. }
  1044. };
  1045. // app.post('/_api/admin/notifications.add' , admin.api.notificationAdd);
  1046. actions.api.notificationAdd = function(req, res) {
  1047. var UpdatePost = crowi.model('UpdatePost');
  1048. var pathPattern = req.body.pathPattern;
  1049. var channel = req.body.channel;
  1050. debug('notification.add', pathPattern, channel);
  1051. UpdatePost.create(pathPattern, channel, req.user)
  1052. .then(function(doc) {
  1053. debug('Successfully save updatePost', doc);
  1054. // fixme: うーん
  1055. doc.creator = doc.creator._id.toString();
  1056. return res.json(ApiResponse.success({updatePost: doc}));
  1057. }).catch(function(err) {
  1058. debug('Failed to save updatePost', err);
  1059. return res.json(ApiResponse.error());
  1060. });
  1061. };
  1062. // app.post('/_api/admin/notifications.remove' , admin.api.notificationRemove);
  1063. actions.api.notificationRemove = function(req, res) {
  1064. var UpdatePost = crowi.model('UpdatePost');
  1065. var id = req.body.id;
  1066. UpdatePost.remove(id)
  1067. .then(function() {
  1068. debug('Successfully remove updatePost');
  1069. return res.json(ApiResponse.success({}));
  1070. }).catch(function(err) {
  1071. debug('Failed to remove updatePost', err);
  1072. return res.json(ApiResponse.error());
  1073. });
  1074. };
  1075. // app.get('/_api/admin/users.search' , admin.api.userSearch);
  1076. actions.api.usersSearch = function(req, res) {
  1077. const User = crowi.model('User');
  1078. const email =req.query.email;
  1079. User.findUsersByPartOfEmail(email, {})
  1080. .then(users => {
  1081. const result = {
  1082. data: users
  1083. };
  1084. return res.json(ApiResponse.success(result));
  1085. }).catch(err => {
  1086. return res.json(ApiResponse.error());
  1087. });
  1088. };
  1089. actions.api.toggleIsEnabledForGlobalNotification = async(req, res) => {
  1090. const id = req.query.id;
  1091. const isEnabled = (req.query.isEnabled == 'true');
  1092. try {
  1093. if (isEnabled) {
  1094. await GlobalNotificationSetting.enable(id);
  1095. }
  1096. else {
  1097. await GlobalNotificationSetting.disable(id);
  1098. }
  1099. return res.json(ApiResponse.success());
  1100. }
  1101. catch (err) {
  1102. return res.json(ApiResponse.error());
  1103. }
  1104. };
  1105. /**
  1106. * save esa settings, update config cache, and response json
  1107. *
  1108. * @param {*} req
  1109. * @param {*} res
  1110. */
  1111. actions.api.importerSettingEsa = async(req, res) => {
  1112. const form = req.form.settingForm;
  1113. if (!req.form.isValid) {
  1114. return res.json({status: false, message: req.form.errors.join('\n')});
  1115. }
  1116. await saveSetting(req, res, form);
  1117. await importer.initializeEsaClient();
  1118. };
  1119. /**
  1120. * save qiita settings, update config cache, and response json
  1121. *
  1122. * @param {*} req
  1123. * @param {*} res
  1124. */
  1125. actions.api.importerSettingQiita = async(req, res) => {
  1126. const form = req.form.settingForm;
  1127. if (!req.form.isValid) {
  1128. return res.json({status: false, message: req.form.errors.join('\n')});
  1129. }
  1130. await saveSetting(req, res, form);
  1131. await importer.initializeQiitaClient();
  1132. };
  1133. /**
  1134. * Import all posts from esa
  1135. *
  1136. * @param {*} req
  1137. * @param {*} res
  1138. */
  1139. actions.api.importDataFromEsa = async(req, res) => {
  1140. const user = req.user;
  1141. let errors;
  1142. try {
  1143. errors = await importer.importDataFromEsa(user);
  1144. }
  1145. catch (err) {
  1146. errors = [err];
  1147. }
  1148. if (errors.length > 0) {
  1149. return res.json({ status: false, message: `<br> - ${errors.join('<br> - ')}` });
  1150. }
  1151. return res.json({ status: true });
  1152. };
  1153. /**
  1154. * Import all posts from qiita
  1155. *
  1156. * @param {*} req
  1157. * @param {*} res
  1158. */
  1159. actions.api.importDataFromQiita = async(req, res) => {
  1160. const user = req.user;
  1161. let errors;
  1162. try {
  1163. errors = await importer.importDataFromQiita(user);
  1164. }
  1165. catch (err) {
  1166. errors = [err];
  1167. }
  1168. if (errors.length > 0) {
  1169. return res.json({ status: false, message: `<br> - ${errors.join('<br> - ')}` });
  1170. }
  1171. return res.json({ status: true });
  1172. };
  1173. /**
  1174. * Test connection to esa and response result with json
  1175. *
  1176. * @param {*} req
  1177. * @param {*} res
  1178. */
  1179. actions.api.testEsaAPI = async(req, res) => {
  1180. try {
  1181. await importer.testConnectionToEsa();
  1182. return res.json({ status: true });
  1183. }
  1184. catch (err) {
  1185. return res.json({ status: false, message: `${err}` });
  1186. }
  1187. };
  1188. /**
  1189. * Test connection to qiita and response result with json
  1190. *
  1191. * @param {*} req
  1192. * @param {*} res
  1193. */
  1194. actions.api.testQiitaAPI = async(req, res) => {
  1195. try {
  1196. await importer.testConnectionToQiita();
  1197. return res.json({ status: true });
  1198. }
  1199. catch (err) {
  1200. return res.json({ status: false, message: `${err}` });
  1201. }
  1202. };
  1203. /**
  1204. * Get Config.showRecentCreatedNumber and response result with json
  1205. *
  1206. * @param {*} req
  1207. * @param {*} res
  1208. */
  1209. actions.api.showRecentCreatedNumber = function(req, res) {
  1210. const config = crowi.getConfig();
  1211. try {
  1212. const showRecentCreatedNumber = Config.showRecentCreatedNumber(config);
  1213. const result = {};
  1214. result.pages = {'showRecentCreatedNumber': showRecentCreatedNumber};
  1215. return res.json(ApiResponse.success(result));
  1216. }
  1217. catch (err) {
  1218. return res.json(ApiResponse.error(err));
  1219. }
  1220. };
  1221. /**
  1222. * save settings, update config cache, and response json
  1223. *
  1224. * @param {any} req
  1225. * @param {any} res
  1226. * @param {any} form
  1227. */
  1228. function saveSetting(req, res, form) {
  1229. Config.updateNamespaceByArray('crowi', form, function(err, config) {
  1230. Config.updateConfigCache('crowi', config);
  1231. return res.json({status: true});
  1232. });
  1233. }
  1234. /**
  1235. * save settings, update config cache ONLY. (this method don't response json)
  1236. *
  1237. * @param {any} form
  1238. * @returns
  1239. */
  1240. function saveSettingAsync(form) {
  1241. return new Promise((resolve, reject) => {
  1242. Config.updateNamespaceByArray('crowi', form, (err, config) => {
  1243. if (err) {
  1244. return reject(err);
  1245. }
  1246. Config.updateConfigCache('crowi', config);
  1247. return resolve();
  1248. });
  1249. });
  1250. }
  1251. function validateMailSetting(req, form, callback) {
  1252. const mailer = crowi.mailer;
  1253. const option = {
  1254. host: form['mail:smtpHost'],
  1255. port: form['mail:smtpPort'],
  1256. };
  1257. if (form['mail:smtpUser'] && form['mail:smtpPassword']) {
  1258. option.auth = {
  1259. user: form['mail:smtpUser'],
  1260. pass: form['mail:smtpPassword'],
  1261. };
  1262. }
  1263. if (option.port === 465) {
  1264. option.secure = true;
  1265. }
  1266. const smtpClient = mailer.createSMTPClient(option);
  1267. debug('mailer setup for validate SMTP setting', smtpClient);
  1268. smtpClient.sendMail({
  1269. from: form['mail:from'],
  1270. to: req.user.email,
  1271. subject: 'Wiki管理設定のアップデートによるメール通知',
  1272. text: 'このメールは、WikiのSMTP設定のアップデートにより送信されています。'
  1273. }, callback);
  1274. }
  1275. return actions;
  1276. };