'use strict' /** * Copyright (c) 2017~2022, OBCon Inc. * All rights reserved. */ /** * @file * @copyright 2017~2022, OBCon Inc. * @author gye hyun james kim [pnuskgh@gmail.com] */ // var multer = require('multer'); // var cors = require('cors'); //--- CORS(Cross-Origin Resource Sharing) const fs = require('fs'); const path = require('path'); const moment = require('moment'); const View = utils.getClass('include', 'view.js'); const markdown = utils.getModule('include', 'Markdown.js'); //--- Facebook 등으로 공유할 때 사용 한다. const share = utils.getModule('openapis', 'share.js'); //--- MediaWiki 등으로 배포할 때 사용 한다. const deploy = utils.getModule('openapis', 'deploy.js'); class MarkdownsView extends View { constructor() { super(); this._name = 'markdowns'; this._title = '마크다운'; this._model = modules.markdowns.model; this._msgs = []; //--- msg 저장을 위한 임시 영역 this._dataSitemap = {}; //--- Outfile 내용 저장을 위한 임시 영역 } //--- 권한 설정 반환 getRights() { return { get: { list : { isLogin: true, isAdmin: true, isGlobalAdmin: true }, detail : { isLogin: true, isAdmin: true, isGlobalAdmin: true }, create : { isLogin: true, isAdmin: true, isGlobalAdmin: true }, update : { isLogin: true, isAdmin: true, isGlobalAdmin: true }, convert : { isLogin: false, isAdmin: false, isGlobalAdmin: false }, createSitemap : { isLogin: true, isAdmin: true, isGlobalAdmin: true } }, post: { create : { isLogin: true, isAdmin: true, isGlobalAdmin: true }, update : { isLogin: true, isAdmin: true, isGlobalAdmin: true }, delete : { isLogin: true, isAdmin: true, isGlobalAdmin: true } }, ajaxGet: { list : { isLogin: true, isAdmin: true, isGlobalAdmin: true }, detail : { isLogin: true, isAdmin: true, isGlobalAdmin: true } }, ajaxPost: { create : { isLogin: true, isAdmin: true, isGlobalAdmin: true }, update : { isLogin: true, isAdmin: true, isGlobalAdmin: true }, delete : { isLogin: true, isAdmin: true, isGlobalAdmin: true }, deploy : { isLogin: true, isAdmin: true, isGlobalAdmin: true } }, //--- To-Do : SocketIO의 권한을 설정 한다. socketio default: false }; } //--- Router 설정 반환 getRouters() { let routers = super.getRouters(); routers.get.convert = this.convert.bind(this); routers.get.createSitemap = this.createSitemap.bind(this); //--- sitemap.md 파일을 생성 한다. routers.ajaxPost.deploy = this.ajaxDeploy.bind(this); return routers; } //--- Markdown 파일을 읽어서 화면에 표시 한다. //--- pages 모듈의 view() 참조 convert(req, res, next) { (req.query.record) ? this._convert_record(req, res, next):this._convert_file(req, res, next); } _convert_file(req, res, next) { const theme = this.getTheme('convert', req, 'convert', 'convert.ejs'); theme.javascriptFiles.push(this.name + '/js/markdown.js'); theme.cssFiles.push(this.name + '/css/markdown.css'); const options = { req: req, res: res, file: req.query.source || '/README.md', //--- Markdown 파일 language: req.query.language || 'ko_KR', //--- 사용 언어 service: req.query.service || '' //--- 강제로 지정한 서비스 이름 } markdown.initialize(options); const filename = path.dirname(markdown.options.file) + '/' + path.basename(markdown.options.file, '.md'); theme.javascriptFiles.push(`${this.name}/js/${config.service.name}/${filename}.js`); theme.cssFiles.push(`${this.name}/css/${config.service.name}/${filename}.css`); //--- Markdown 파일 조회시 로그인이 필요한 경우, 로그인 화면을 표시 한다. if ((markdown.config.isPublic == false) && ((!req.session.isLogined) || (req.session.isLogined == false))) { res.redirect(config.http.path + '/users?action=login'); return; } theme.layout = markdown.config.layout || theme.layout; share.url = utils.getFullUrl(req.url); share.title = config.openAPI.share.title; const info = markdown.convert({ source: 'file', target: 'info' }); info.filestat = fs.statSync(info.filename); info.filestat.mtimeStr = moment(info.filestat.mtime).format('YYYY-MM-DD HH:mm:ss'); const data = { folder: markdown.folder || '', menu: markdown.convert({ source: 'menu', target: 'menu' }), index: JSON.parse('[' + (' ' + markdown.convert({ source: 'file', target: 'index' })).slice(0, -1) + ']'), content: markdown.convert({ source: 'file', target: 'html' }), info: info, config: markdown.config, item: null, share: share.getHtml(), deploy: deploy.getHtml(), record: -1, source: options.file, language: options.language }; res.render(utils.getTemplateFullPath(theme.serviceInfo.site.id, theme.layout), { theme: theme, baseUrl: req.baseUrl, session: req.session, data: data }); } //--- 사용하는 곳이 없음 _convert_record(req, res, next) { let theme = this.getTheme('convert', req, 'convert', 'convert.ejs'); theme.javascriptFiles.push(this.name + '/js/markdown.js'); theme.cssFiles.push(this.name + '/css/markdown.css'); let record = req.query.record; let query = { where: { id: record, deleted: false } }; this.model.table.findOne(query) .then(function(item) { if (!item) { next(); return; } if (item.isPublic == false) { //--- Site ID가 일치하여야 한다. if (item.site != req.session.site.id) { next(); return; } //--- 로그인이 되어 있어야 한다. if ((!req.session.isLogined) || (req.session.isLogined == false)) { res.redirect(config.http.path + '/users?action=login'); return; } } //--- To-Do : service 항목 추가에 따른 수정을 할 것. _convert_file() 함수 참조 const options = { req: req, res: res, source: path.join(item.folder, `record=${record}.md`), //--- ???, markdown 파일 이름 (예, /HAProxy/README.md) language: item.language, //--- markdown 파일 언어 service: '' //--- Service 이름 } markdown.initialize(options); share.url = utils.getFullUrl(req.url); share.title = config.openAPI.share.title; const data = { folder: item.folder, menu: markdown.convert({ source: 'menu', target: 'menu' }), index: markdown.convert({ source: 'content', content: item.content, target: 'index' }), content: markdown.convert({ source: 'content', content: item.content, target: 'html' }), config: { layout: config.theme.layout, //--- layout isPublic: item.isPublic //--- false. 로그인 필요 }, item: item, share: share.getHtml(), deploy: deploy.getHtml(), record: record, source: options.source, language: options.language }; theme.javascriptFiles.push(`${this.name}/js/${config.service.name}/record_${record}.js`); theme.cssFiles.push(`${this.name}/css/${config.service.name}/record_${record}.css`); res.render(utils.getTemplateFullPath(theme.serviceInfo.site.id, config.theme.layout), { theme: theme, baseUrl: req.baseUrl, session: req.session, data: data }); }).catch(utils.obj.loggerError); } //--- 사용하는 곳이 없음 createSitemap(req, res, next) { let theme = this.getTheme('createSitemap', req, 'createSitemap', 'createSitemap.ejs'); //--- manual/${service}/default/ko_KR/sitemap.md //--- manual/${service}/${site}/ko_KR/sitemap.md //--- manual/${service}/${site}/${lang}/sitemap.md let folderManual = path.join(config.folder_root, 'manual'); let service = ''; let site = ''; let lang = ''; this._msgs = []; this._dataSitemap = {}; let dataService = fs.readdirSync(folderManual); for (let idxService = 0; idxService < dataService.length; idxService++) { service = dataService[idxService]; let folderService = path.join(folderManual, service); if (fs.statSync(folderService).isDirectory() == false) { continue; } let dataSite = fs.readdirSync(folderService); for (let idxSite = 0; idxSite < dataSite.length; idxSite++) { site = dataSite[idxSite]; let folderSite = path.join(folderService, site); if (fs.statSync(folderSite).isDirectory() == false) { continue; } let dataLang = fs.readdirSync(folderSite); for (let idxLang = 0; idxLang < dataLang.length; idxLang++) { lang = dataLang[idxLang]; let folderLang = path.join(folderSite, lang); if (fs.statSync(folderLang).isDirectory() == false) { continue; } this._createSitemap(req, res, next, folderManual, service, site, lang); } } } let data = { code: '0', msg: this._msgs.join('
') }; res.render(utils.getTemplateFullPath(theme.serviceInfo.site.id, config.theme.layout), { theme: theme, baseUrl: req.baseUrl, session: req.session, data: data }); } //--- 언어별로 사이트맵을 작성 한다. //--- To-Do: 1 단계 - default/ko_KR/에 대한 사이트맵을 작성 한다. //--- To-Do: 2 단계 - 언어별 사이트맵을 작성 한다. //--- To-Do: 3 단계 - 사이트별 사이트맵을 작성 한다. _createSitemap(req, res, next, folderManual, service, site, lang) { this._msgs.push('  service: ' + service + ', site: ' + site + ', lang: ' + lang + ' sitemap 작성 시작'); if (utils.existField(this._dataSitemap, service) == false) { this._dataSitemap[service] = {}; } if (utils.existField(this._dataSitemap[service], site) == false) { this._dataSitemap[service][site] = {}; } if (utils.existField(this._dataSitemap[service][site], lang) == false) { this._dataSitemap[service][site][lang] = []; } //--- 폴더와 파일 정보를 읽어서 this._dataSitemap[service][site][lang]에 저장 한다. logger.info(`Create sitemap : service - ${service}, site - ${site}, lang - ${lang}`); this._searchFolder(folderManual, service, site, lang, path.join(folderManual, service, site, lang)); // logger.info('Create sitemap : dataSitemap - ' + JSON.stringify(this._dataSitemap[service][site][lang])); let saveFolder = ''; let contents = [] ; contents.push('## Sitemap'); contents.push(''); for (let idx = 0; idx < this._dataSitemap[service][site][lang].length; idx++) { let item = this._dataSitemap[service][site][lang][idx]; logger.info('Sitemap : ' + JSON.stringify(item)); if (item.folder == '/') { if (item.conf.isPublic) { contents.push(item.ident + '- [' + item.file_name + '](' + item.folder + item.file_name + '.md)'); logger.info(' ' + item.ident + '- [' + item.file_name + '](' + item.folder + item.file_name + '.md)'); } else { contents.push(item.ident + '- [' + item.file_name + '](' + item.folder + item.file_name + '.md)'); logger.info(' ' + item.ident + '- [' + item.file_name + '](' + item.folder + item.file_name + '.md)'); } } else { if (saveFolder != item.folder) { saveFolder = item.folder; if (item.file_name == 'README') { if (item.conf.isPublic) { contents.push(item.ident.substring(2) + '- [' + item.folder.substring(1, item.folder.length - 1) + '](' + item.folder + item.file_name + '.md)'); logger.info(' ' + item.ident.substring(2) + '- [' + item.folder.substring(1, item.folder.length - 1) + '](' + item.folder + item.file_name + '.md)'); } else { contents.push(item.ident.substring(2) + '- [' + item.folder.substring(1, item.folder.length - 1) + '](' + item.folder + item.file_name + '.md)'); logger.info(' ' + item.ident.substring(2) + '- [' + item.folder.substring(1, item.folder.length - 1) + '](' + item.folder + item.file_name + '.md)'); } continue; } else { contents.push(item.ident.substring(2) + '- ' + item.folder.substring(1, item.folder.length - 1)); logger.info(' ' + item.ident.substring(2) + '- ' + item.folder.substring(1, item.folder.length - 1)); } } if (item.conf.isPublic) { contents.push(item.ident + '- [' + item.file_name + '](' + item.folder + item.file_name + '.md)'); logger.info(' ' + item.ident + '- [' + item.file_name + '](' + item.folder + item.file_name + '.md)'); } else { contents.push(item.ident + '- [' + item.file_name + '](' + item.folder + item.file_name + '.md)'); logger.info(' ' + item.ident + '- [' + item.file_name + '](' + item.folder + item.file_name + '.md)'); } } } // let fout_filename = path.join(folderManual, service, site, lang, '_sitemap.md'); let fout_filename = path.join(folderManual, service, site, lang, 'sitemap.md'); if (fs.existsSync(fout_filename)) { fs.unlinkSync(fout_filename); } if (utils.isLinux()) { fs.writeFileSync(fout_filename, contents.join('\n'), 'utf8'); } else { fs.writeFileSync(fout_filename, contents.join('\r\n'), 'utf8'); } } _searchFolder(folderManual, service, site, lang, folder=null, folderName='/', ident='') { // this._msgs.push(ident + 'Folder : ' + folderName); let data = fs.readdirSync(folder); //--- 파일 처리 let file = path.join(folder, 'README.md'); if ((fs.existsSync(file)) && (fs.statSync(file).isFile())) { let conf = this._getMarkdownConfig(file); this._dataSitemap[service][site][lang].push({ ident: ident, folder: folderName, file_name: 'README', conf: conf }); } for (let idx = 0; idx < data.length; idx++) { let file = path.join(folder, data[idx]); if ((fs.statSync(file).isFile()) && (file.endsWith('.md'))) { let filename = data[idx]; let file_ext = path.extname(filename); //--- .md let file_name = path.basename(file, file_ext); if ((file_name.startsWith('zz')) || (file_name.startsWith('_'))) { continue; } if ((file_name == 'README') || (file_name.startsWith('MENU_')) || (file_name == 'sitemap')) { continue; } try { let conf = this._getMarkdownConfig(file); // this._msgs.push(ident + 'Service : ' + service + ', Folder : ' + folderName + ', file : ' + file_name + ', conf : ' + conf.isPublic); this._dataSitemap[service][site][lang].push({ ident: ident, folder: folderName, file_name: file_name, conf: conf }); } catch (e) { console.error(file + ' :: error.'); console.error(e.stack); } } } //--- 폴더 처리 for (let idx = 0; idx < data.length; idx++) { let file = path.join(folder, data[idx]); if (data[idx].startsWith('zz')) { continue; } if (fs.statSync(file).isDirectory()) { this._searchFolder(folderManual, service, site, lang, file, folderName + data[idx] + '/', ident + ' '); } } } _getMarkdownConfig(file) { let rtConfig = markdown._defaultConfig; let tmpFile = path.join(path.dirname(file), 'config.js'); if (fs.existsSync(tmpFile)) { delete require.cache[tmpFile]; let conf = require(tmpFile); Object.assign(rtConfig, conf); } tmpFile = path.join(path.dirname(file), path.basename(file, '.md') + '.js'); if (fs.existsSync(tmpFile)) { delete require.cache[tmpFile]; let conf = require(tmpFile); Object.assign(rtConfig, conf); } return rtConfig; } ajaxDeploy(req, res, next) { let id = req.body.id; if ((typeof(id) == 'undefined') || (id == '')) { utils.obj.resJson(res, 1, 'ID가 없습니다.', {}, {}); return; } let service = null; for (let idx = 0; idx < config.openAPI.deploy.services.length; idx++) { let tmpService = config.openAPI.deploy.services[idx]; if (tmpService.id == id) { service = tmpService; break; } } if (service == null) { utils.obj.resJson(res, 2, 'ID에 해당하는 서비스가 없습니다.', {}, {}); return; } let opts = { req: req, res: res, service: service, record: req.body.record, source: req.body.source, language: req.body.language } deploy.deploy(opts, function(code, message, data, err) { utils.obj.resJson(res, code, message, data, err); }); } } module.exports = MarkdownsView;