/* eslint-disable no-use-before-define */ module.exports = function(crowi, app) { const debug = require('debug')('growi:routes:admin'); const logger = require('@alias/logger')('growi:routes:admin'); const models = crowi.models; const UserGroup = models.UserGroup; const UserGroupRelation = models.UserGroupRelation; const GlobalNotificationSetting = models.GlobalNotificationSetting; const { configManager, aclService, slackNotificationService, exportService, } = crowi; const recommendedWhitelist = require('@commons/service/xss/recommended-whitelist'); const ApiResponse = require('../util/apiResponse'); const importer = require('../util/importer')(crowi); const MAX_PAGE_LIST = 50; const actions = {}; const { check } = require('express-validator'); const api = {}; function createPager(total, limit, page, pagesCount, maxPageList) { const pager = { page, pagesCount, pages: [], total, previous: null, previousDots: false, next: null, nextDots: false, }; if (page > 1) { pager.previous = page - 1; } if (page < pagesCount) { pager.next = page + 1; } let pagerMin = Math.max(1, Math.ceil(page - maxPageList / 2)); let pagerMax = Math.min(pagesCount, Math.floor(page + maxPageList / 2)); if (pagerMin === 1) { if (MAX_PAGE_LIST < pagesCount) { pagerMax = MAX_PAGE_LIST; } else { pagerMax = pagesCount; } } if (pagerMax === pagesCount) { if ((pagerMax - MAX_PAGE_LIST) < 1) { pagerMin = 1; } else { pagerMin = pagerMax - MAX_PAGE_LIST; } } pager.previousDots = null; if (pagerMin > 1) { pager.previousDots = true; } pager.nextDots = null; if (pagerMax < pagesCount) { pager.nextDots = true; } for (let i = pagerMin; i <= pagerMax; i++) { pager.pages.push(i); } return pager; } actions.index = function(req, res) { return res.render('admin/index'); }; // app.get('/admin/app' , admin.app.index); actions.app = {}; actions.app.index = function(req, res) { return res.render('admin/app'); }; actions.app.settingUpdate = function(req, res) { }; // app.get('/admin/security' , admin.security.index); actions.security = {}; actions.security.index = function(req, res) { const isWikiModeForced = aclService.isWikiModeForced(); const guestModeValue = aclService.getGuestModeValue(); return res.render('admin/security', { isWikiModeForced, guestModeValue, }); }; // app.get('/admin/markdown' , admin.markdown.index); actions.markdown = {}; actions.markdown.index = function(req, res) { const markdownSetting = configManager.getConfigByPrefix('markdown', 'markdown:'); return res.render('admin/markdown', { markdownSetting, recommendedWhitelist, }); }; // app.get('/admin/customize' , admin.customize.index); actions.customize = {}; actions.customize.index = function(req, res) { const settingForm = configManager.getConfigByPrefix('crowi', 'customize:'); // TODO delete after apiV3 /* eslint-disable quote-props, no-multi-spaces */ const highlightJsCssSelectorOptions = { 'github': { name: '[Light] GitHub', border: false }, 'github-gist': { name: '[Light] GitHub Gist', border: true }, 'atom-one-light': { name: '[Light] Atom One Light', border: true }, 'xcode': { name: '[Light] Xcode', border: true }, 'vs': { name: '[Light] Vs', border: true }, 'atom-one-dark': { name: '[Dark] Atom One Dark', border: false }, 'hybrid': { name: '[Dark] Hybrid', border: false }, 'monokai': { name: '[Dark] Monokai', border: false }, 'tomorrow-night': { name: '[Dark] Tomorrow Night', border: false }, 'vs2015': { name: '[Dark] Vs 2015', border: false }, }; /* eslint-enable quote-props, no-multi-spaces */ return res.render('admin/customize', { settingForm, highlightJsCssSelectorOptions, }); }; // app.get('/admin/notification' , admin.notification.index); actions.notification = {}; actions.notification.index = async(req, res) => { return res.render('admin/notification'); }; // app.get('/admin/notification/slackAuth' , admin.notification.slackauth); actions.notification.slackAuth = function(req, res) { const code = req.query.code; const { t } = req; if (!code || !slackNotificationService.hasSlackConfig()) { return res.redirect('/admin/notification'); } const slack = crowi.slack; slack.getOauthAccessToken(code) .then(async(data) => { debug('oauth response', data); try { await configManager.updateConfigsInTheSameNamespace('notification', { 'slack:token': data.access_token }); req.flash('successMessage', [t('message.successfully_connected')]); } catch (err) { req.flash('errorMessage', [t('message.fail_to_save_access_token')]); } return res.redirect('/admin/notification'); }) .catch((err) => { debug('oauth response ERROR', err); req.flash('errorMessage', [t('message.fail_to_fetch_access_token')]); return res.redirect('/admin/notification'); }); }; // app.post('/admin/notification/slackSetting/disconnect' , admin.notification.disconnectFromSlack); actions.notification.disconnectFromSlack = async function(req, res) { await configManager.updateConfigsInTheSameNamespace('notification', { 'slack:token': '' }); req.flash('successMessage', [req.t('successfully_disconnected')]); return res.redirect('/admin/notification'); }; actions.globalNotification = {}; actions.globalNotification.detail = async(req, res) => { const notificationSettingId = req.params.id; let globalNotification; if (notificationSettingId) { try { globalNotification = await GlobalNotificationSetting.findOne({ _id: notificationSettingId }); } catch (err) { logger.error(`Error in finding a global notification setting with {_id: ${notificationSettingId}}`); } } return res.render('admin/global-notification-detail', { globalNotification }); }; actions.search = {}; actions.search.index = function(req, res) { return res.render('admin/search', {}); }; actions.user = {}; actions.user.index = async function(req, res) { return res.render('admin/users'); }; actions.externalAccount = {}; actions.externalAccount.index = function(req, res) { return res.render('admin/external-accounts'); }; actions.userGroup = {}; actions.userGroup.index = function(req, res) { const page = parseInt(req.query.page) || 1; const isAclEnabled = aclService.isAclEnabled(); const renderVar = { userGroups: [], userGroupRelations: new Map(), pager: null, isAclEnabled, }; UserGroup.findUserGroupsWithPagination({ page }) .then((result) => { const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST); const userGroups = result.docs; renderVar.userGroups = userGroups; renderVar.pager = pager; return userGroups.map((userGroup) => { return new Promise((resolve, reject) => { UserGroupRelation.findAllRelationForUserGroup(userGroup) .then((relations) => { return resolve({ id: userGroup._id, relatedUsers: relations.map((relation) => { return relation.relatedUser; }), }); }); }); }); }) .then((allRelationsPromise) => { return Promise.all(allRelationsPromise); }) .then((relations) => { for (const relation of relations) { renderVar.userGroupRelations[relation.id] = relation.relatedUsers; } debug('in findUserGroupsWithPagination findAllRelationForUserGroupResult', renderVar.userGroupRelations); return res.render('admin/user-groups', renderVar); }) .catch((err) => { debug('Error on find all relations', err); return res.json(ApiResponse.error('Error')); }); }; // グループ詳細 actions.userGroup.detail = async function(req, res) { const userGroupId = req.params.id; const userGroup = await UserGroup.findOne({ _id: userGroupId }); if (userGroup == null) { logger.error('no userGroup is exists. ', userGroupId); return res.redirect('/admin/user-groups'); } return res.render('admin/user-group-detail', { userGroup }); }; // Importer management actions.importer = {}; actions.importer.api = api; api.validators = {}; api.validators.importer = {}; actions.importer.index = function(req, res) { const settingForm = configManager.getConfigByPrefix('crowi', 'importer:'); return res.render('admin/importer', { settingForm, }); }; api.validators.importer.esa = function() { const validator = [ check('importer:esa:team_name').not().isEmpty().withMessage('Error. Empty esa:team_name'), check('importer:esa:access_token').not().isEmpty().withMessage('Error. Empty esa:access_token'), ]; return validator; }; api.validators.importer.qiita = function() { const validator = [ check('importer:qiita:team_name').not().isEmpty().withMessage('Error. Empty qiita:team_name'), check('importer:qiita:access_token').not().isEmpty().withMessage('Error. Empty qiita:access_token'), ]; return validator; }; // Export management actions.export = {}; actions.export.index = (req, res) => { return res.render('admin/export'); }; actions.export.download = (req, res) => { // TODO: add express validator const { fileName } = req.params; try { const zipFile = exportService.getFile(fileName); return res.download(zipFile); } catch (err) { // TODO: use ApiV3Error logger.error(err); return res.json(ApiResponse.error()); } }; actions.api = {}; /** * save esa settings, update config cache, and response json * * @param {*} req * @param {*} res */ actions.api.importerSettingEsa = async(req, res) => { const form = req.body; const { validationResult } = require('express-validator'); const errors = validationResult(req); if (!errors.isEmpty()) { return res.json(ApiResponse.error('esa.io form is blank')); } await configManager.updateConfigsInTheSameNamespace('crowi', form); importer.initializeEsaClient(); // let it run in the back aftert res return res.json(ApiResponse.success()); }; /** * save qiita settings, update config cache, and response json * * @param {*} req * @param {*} res */ actions.api.importerSettingQiita = async(req, res) => { const form = req.body; const { validationResult } = require('express-validator'); const errors = validationResult(req); if (!errors.isEmpty()) { return res.json(ApiResponse.error('Qiita form is blank')); } await configManager.updateConfigsInTheSameNamespace('crowi', form); importer.initializeQiitaClient(); // let it run in the back aftert res return res.json(ApiResponse.success()); }; /** * Import all posts from esa * * @param {*} req * @param {*} res */ actions.api.importDataFromEsa = async(req, res) => { const user = req.user; let errors; try { errors = await importer.importDataFromEsa(user); } catch (err) { errors = [err]; } if (errors.length > 0) { return res.json(ApiResponse.error(`
- ${errors.join('
- ')}`)); } return res.json(ApiResponse.success()); }; /** * Import all posts from qiita * * @param {*} req * @param {*} res */ actions.api.importDataFromQiita = async(req, res) => { const user = req.user; let errors; try { errors = await importer.importDataFromQiita(user); } catch (err) { errors = [err]; } if (errors.length > 0) { return res.json(ApiResponse.error(`
- ${errors.join('
- ')}`)); } return res.json(ApiResponse.success()); }; /** * Test connection to esa and response result with json * * @param {*} req * @param {*} res */ actions.api.testEsaAPI = async(req, res) => { try { await importer.testConnectionToEsa(); return res.json(ApiResponse.success()); } catch (err) { return res.json(ApiResponse.error(err)); } }; /** * Test connection to qiita and response result with json * * @param {*} req * @param {*} res */ actions.api.testQiitaAPI = async(req, res) => { try { await importer.testConnectionToQiita(); return res.json(ApiResponse.success()); } catch (err) { return res.json(ApiResponse.error(err)); } }; actions.api.searchBuildIndex = async function(req, res) { const search = crowi.getSearcher(); if (!search) { return res.json(ApiResponse.error('ElasticSearch Integration is not set up.')); } try { search.buildIndex(); } catch (err) { return res.json(ApiResponse.error(err)); } return res.json(ApiResponse.success()); }; return actions; };