├── .autod.conf.js ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .theia └── settings.json ├── .travis.yml ├── README.md ├── app ├── controller │ ├── admin.js │ ├── block.js │ ├── file.js │ ├── home.js │ └── user.js ├── middleware │ └── error_handler.js ├── model │ ├── block.js │ ├── file.js │ └── user.js ├── router.js ├── service │ ├── block.js │ ├── file.js │ └── user.js └── view │ ├── home.nj │ ├── lowcode.nj │ ├── preview.nj │ └── renderer.nj ├── appveyor.yml ├── bootstrap ├── config ├── config.default.js ├── config.prod.js ├── plugin.js └── secret.js ├── jsconfig.json ├── package.json └── test └── app └── controller └── home.test.js /.autod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | write: true, 5 | prefix: '^', 6 | plugin: 'autod-egg', 7 | test: [ 8 | 'test', 9 | 'benchmark', 10 | ], 11 | dep: [ 12 | 'egg', 13 | 'egg-scripts', 14 | ], 15 | devdep: [ 16 | 'egg-ci', 17 | 'egg-bin', 18 | 'egg-mock', 19 | 'autod', 20 | 'autod-egg', 21 | 'eslint', 22 | 'eslint-config-egg', 23 | ], 24 | exclude: [ 25 | './test/fixtures', 26 | './dist', 27 | ], 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | npm-debug.log 3 | yarn-error.log 4 | node_modules/ 5 | package-lock.json 6 | yarn.lock 7 | coverage/ 8 | .idea/ 9 | run/ 10 | .DS_Store 11 | *.sw* 12 | *.un~ 13 | typings/ 14 | .nyc_output/ 15 | -------------------------------------------------------------------------------- /.theia/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "**/.ds": true, 9 | "**/.theia": true 10 | }, 11 | "editor.autoSave": "on", 12 | "terminal.integrated.shell.linux": "/bin/bash" 13 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: node_js 3 | node_js: 4 | - '10' 5 | before_install: 6 | - npm i npminstall -g 7 | install: 8 | - npminstall 9 | script: 10 | - npm run ci 11 | after_script: 12 | - npminstall codecov && codecov 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Documents Solution Site 2 | 3 | *before start, you need to fill yuque token and aliyun secret in config.default.js .* -------------------------------------------------------------------------------- /app/controller/admin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const stringToStream = require('string-to-stream'); 3 | 4 | const Controller = require('egg').Controller; 5 | 6 | class AdminController extends Controller { 7 | async lowcode() { 8 | const { ctx } = this; 9 | await ctx.render('lowcode.nj'); 10 | } 11 | 12 | async preview() { 13 | const { ctx } = this; 14 | await ctx.render('preview.nj'); 15 | } 16 | 17 | async schema() { 18 | const { ctx } = this; 19 | const res = await ctx.oss.get('portal/schema.json'); 20 | console.log('res: ', res); 21 | ctx.response.set('content-type', 'json'); 22 | ctx.body = res.content; 23 | } 24 | 25 | async saveSchema() { 26 | const { ctx } = this; 27 | const { page = 'home', schema } = ctx.request.body; 28 | const filename = `${page}.json`; 29 | ctx.response.set('Access-Control-Allow-Origin', '*'); 30 | const res = await ctx.oss.putStream(`portal/${filename}`, stringToStream(JSON.stringify(schema))); 31 | ctx.body = res || 'ok'; 32 | } 33 | 34 | async loginView() { 35 | const { ctx } = this; 36 | const { redirectUrl = '/' } = ctx.request.query; 37 | if (ctx.session.username) { 38 | ctx.redirect(redirectUrl); 39 | return; 40 | } 41 | await ctx.render('renderer.nj', { 42 | data: { 43 | schemaUrl: 'https://i.ablula.tech/portal/login.json', 44 | fullScreen: true, 45 | }, 46 | }); 47 | } 48 | 49 | async logout() { 50 | const { ctx } = this; 51 | ctx.session = null; 52 | ctx.redirect(ctx.get('referer') || '/'); 53 | } 54 | 55 | async login() { 56 | const { ctx } = this; 57 | const { username, password } = ctx.request.body; 58 | const result = await ctx.service.user.login(username, password); 59 | console.log('result: ', result); 60 | ctx.body = { 61 | status: result ? 'success' : 'failed', 62 | }; 63 | } 64 | 65 | } 66 | 67 | module.exports = AdminController; 68 | -------------------------------------------------------------------------------- /app/controller/block.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Controller = require('egg').Controller; 4 | 5 | class BlockController extends Controller { 6 | 7 | async listBlocks() { 8 | const { ctx } = this; 9 | const blocks = await ctx.service.block.listBlock(); 10 | ctx.body = { 11 | code: 0, 12 | data: blocks, 13 | }; 14 | } 15 | async getBlock() { 16 | const { ctx } = this; 17 | const { id } = ctx.params; 18 | const block = await ctx.service.block.getBlockByPk(id); 19 | ctx.body = { 20 | code: 0, 21 | data: block, 22 | }; 23 | } 24 | async createBlock() { 25 | const { ctx } = this; 26 | const { block } = ctx.request.body; 27 | const result = await ctx.service.block.createBlock(block); 28 | ctx.logger.info('create block result: ', result); 29 | ctx.body = { 30 | code: 0, 31 | message: '创建区块成功', 32 | }; 33 | } 34 | async updateBlock() { 35 | const { ctx } = this; 36 | ctx.logger.info('in update block'); 37 | const { id } = ctx.params; 38 | const { block } = ctx.request.body; 39 | ctx.logger.info('update block id: %s, block: %s', id, block); 40 | const result = await ctx.service.block.updateBlock(id, block); 41 | ctx.logger.info('update block result: ', result); 42 | ctx.body = { 43 | code: 0, 44 | message: '更新区块成功', 45 | }; 46 | } 47 | async deleteBlock() { 48 | const { ctx } = this; 49 | const { id } = ctx.params; 50 | const result = await ctx.service.block.deleteBlock(id); 51 | ctx.logger.info('delete block result: ', result); 52 | ctx.body = { 53 | code: 0, 54 | message: '删除区块成功', 55 | }; 56 | } 57 | 58 | } 59 | 60 | module.exports = BlockController; 61 | -------------------------------------------------------------------------------- /app/controller/file.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Controller = require('egg').Controller; 4 | 5 | class AdminController extends Controller { 6 | 7 | async listFiles() { 8 | const { ctx } = this; 9 | const files = await ctx.service.file.listFile(); 10 | ctx.body = { 11 | code: 0, 12 | data: files, 13 | }; 14 | } 15 | async getFile() { 16 | const { ctx } = this; 17 | const { id } = ctx.params; 18 | const file = await ctx.service.file.getFileByPk(id); 19 | ctx.body = { 20 | code: 0, 21 | data: file, 22 | }; 23 | } 24 | async createFile() { 25 | const { ctx } = this; 26 | const { file } = ctx.request.body; 27 | const result = await ctx.service.file.createFile(file); 28 | ctx.logger.info('create file result: ', result); 29 | ctx.body = { 30 | code: 0, 31 | message: '创建文件成功', 32 | }; 33 | } 34 | async updateFile() { 35 | const { ctx } = this; 36 | ctx.logger.info('in update file'); 37 | const { id } = ctx.params; 38 | const { file } = ctx.request.body; 39 | ctx.logger.info('update file id: %s, file: %s', id, file); 40 | const result = await ctx.service.file.updateFile(id, file); 41 | ctx.logger.info('update file result: ', result); 42 | ctx.body = { 43 | code: 0, 44 | message: '更新文件成功', 45 | }; 46 | } 47 | async deleteFile() { 48 | const { ctx } = this; 49 | const { id } = ctx.params; 50 | const result = await ctx.service.file.deleteFile(id); 51 | ctx.logger.info('delete file result: ', result); 52 | ctx.body = { 53 | code: 0, 54 | message: '删除文件成功', 55 | }; 56 | } 57 | 58 | } 59 | 60 | module.exports = AdminController; 61 | -------------------------------------------------------------------------------- /app/controller/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Controller = require('egg').Controller; 4 | 5 | class HomeController extends Controller { 6 | async index() { 7 | const { ctx } = this; 8 | const { yuqueViewer } = this.config; 9 | const { 10 | menuDataSource, 11 | darkMode, 12 | headerHeight, 13 | blackColor, 14 | lightColor, 15 | hideLogo, 16 | hideTitle, 17 | logo, 18 | homepage, 19 | logoHref, 20 | logoStyle, 21 | title, 22 | titleHref, 23 | } = yuqueViewer; 24 | 25 | await ctx.render('renderer.nj', { 26 | data: { 27 | schemaUrl: 'https://i.ablula.tech/portal/home.json', 28 | menuDataSource, 29 | darkMode, 30 | headerHeight, 31 | blackColor, 32 | lightColor, 33 | hideLogo, 34 | hideTitle, 35 | logo, 36 | homepage, 37 | logoHref, 38 | logoStyle, 39 | title, 40 | titleHref, 41 | }, 42 | }); 43 | } 44 | } 45 | 46 | module.exports = HomeController; 47 | -------------------------------------------------------------------------------- /app/controller/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Controller = require('egg').Controller; 4 | 5 | class AdminController extends Controller { 6 | 7 | async profile() { 8 | const { ctx } = this; 9 | ctx.body = { 10 | username: 'admin', 11 | gender: 3, 12 | avatar: 'https://img.alicdn.com/imgextra/i4/O1CN013w2bmQ25WAIha4Hx9_!!6000000007533-55-tps-137-26.svg', 13 | phone: 123456, 14 | }; 15 | } 16 | 17 | } 18 | 19 | module.exports = AdminController; 20 | -------------------------------------------------------------------------------- /app/middleware/error_handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = () => { 4 | return async function errorHandler(ctx, next) { 5 | try { 6 | await next(); 7 | } catch (e) { 8 | console.log('e: ', e); 9 | ctx.body = { 10 | status: 'failed', 11 | }; 12 | } 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /app/model/block.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = app => { 4 | const { INTEGER, STRING, DATE } = app.Sequelize; 5 | 6 | const lowcodeBlock = app.model.define('lowcode-block', { 7 | id: { 8 | type: INTEGER, 9 | autoIncrement: true, 10 | primaryKey: true, 11 | }, 12 | name: STRING, 13 | title: INTEGER, 14 | screenshot: STRING, 15 | schema: STRING, 16 | created_at: DATE, 17 | updated_at: DATE, 18 | }, { 19 | tableName: 'lowcode-block', 20 | }); 21 | return lowcodeBlock; 22 | }; 23 | -------------------------------------------------------------------------------- /app/model/file.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = app => { 4 | const { INTEGER, STRING, DATE } = app.Sequelize; 5 | 6 | const lowcodeFile = app.model.define('lowcode-file', { 7 | id: { 8 | type: INTEGER, 9 | autoIncrement: true, 10 | primaryKey: true, 11 | }, 12 | name: STRING, 13 | parent_id: INTEGER, 14 | type: INTEGER, 15 | icon: STRING, 16 | locator: STRING, 17 | creator: STRING, 18 | schema_url: STRING, 19 | created_at: DATE, 20 | updated_at: DATE, 21 | }, { 22 | tableName: 'lowcode-file', 23 | }); 24 | return lowcodeFile; 25 | }; 26 | -------------------------------------------------------------------------------- /app/model/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = app => { 4 | const { INTEGER, STRING, BOOLEAN, DATE } = app.Sequelize; 5 | 6 | const User = app.model.define('user', { 7 | id: { 8 | type: INTEGER, 9 | autoIncrement: true, 10 | primaryKey: true, 11 | }, 12 | name: STRING, 13 | password: STRING, 14 | gender: BOOLEAN, 15 | phone: STRING, 16 | introduction: STRING, 17 | created_at: DATE, 18 | updated_at: DATE, 19 | }); 20 | return User; 21 | }; 22 | -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const needLogin = async (ctx, next) => { 4 | if (ctx.session.username) { 5 | await next(); 6 | } else { 7 | ctx.redirect(`/login?redirectUrl=${ctx.request.path}`); 8 | } 9 | }; 10 | 11 | /** 12 | * @param {Egg.Application} app - egg application 13 | */ 14 | module.exports = app => { 15 | const { router, controller } = app; 16 | router.get('/', controller.home.index); 17 | router.get('/admin/lowcode', needLogin, controller.admin.lowcode); 18 | router.get('/admin/schema.json', controller.admin.schema); 19 | router.get('/api/v1/schemas/1', controller.admin.schema); 20 | router.get('/admin/preview.html', controller.admin.preview); 21 | router.get('/admin/preview.html/*', controller.admin.preview); 22 | router.post('/api/v1/schemas', controller.admin.saveSchema); 23 | 24 | /** login start */ 25 | 26 | router.get('/login', controller.admin.loginView); 27 | router.get('/logout', controller.admin.logout); 28 | router.post('/api/login', controller.admin.login); 29 | 30 | /** login end */ 31 | 32 | router.get('/api/v1/profile', needLogin, controller.user.profile); 33 | 34 | /** lowcode file start */ 35 | router.get('/api/v1/files', controller.file.listFiles); 36 | router.get('/api/v1/files/:id', controller.file.getFile); 37 | router.post('/api/v1/files', controller.file.createFile); 38 | router.patch('/api/v1/files/:id', controller.file.updateFile); 39 | router.delete('/api/v1/files/:id', controller.file.deleteFile); 40 | /** lowcode file end */ 41 | 42 | /** lowcode block start */ 43 | router.get('/api/v1/blocks', controller.block.listBlocks); 44 | router.post('/api/v1/blocks', controller.block.createBlock); 45 | /** lowcode block end */ 46 | 47 | }; 48 | -------------------------------------------------------------------------------- /app/service/block.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Service } = require('egg'); 4 | 5 | class BlockService extends Service { 6 | 7 | constructor(app) { 8 | super(app); 9 | const { ctx } = this; 10 | this.Block = ctx.model.Block; 11 | } 12 | 13 | async listBlock() { 14 | const { ctx } = this; 15 | return this.Block.findAll(); 16 | } 17 | async getBlockByPk(id) { 18 | return this.Block.findByPk(id); 19 | } 20 | async createBlock(file) { 21 | const { ctx } = this; 22 | return this.Block.create(file); 23 | } 24 | async updateBlock(id, file) { 25 | const { ctx } = this; 26 | return this.Block.update(file, { where: { id } }); 27 | } 28 | async deleteBlock(id) { 29 | const { ctx } = this; 30 | return this.Block.destroy({ where: { id } }); 31 | } 32 | 33 | } 34 | 35 | module.exports = BlockService; 36 | -------------------------------------------------------------------------------- /app/service/file.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Service } = require('egg'); 4 | 5 | class FileService extends Service { 6 | 7 | constructor(app) { 8 | super(app); 9 | const { ctx } = this; 10 | this.File = ctx.model.File; 11 | } 12 | 13 | async listFile() { 14 | const { ctx } = this; 15 | return this.File.findAll(); 16 | } 17 | async getFileByPk(id) { 18 | return this.File.findByPk(id); 19 | } 20 | async createFile(file) { 21 | const { ctx } = this; 22 | return this.File.create(file); 23 | } 24 | async updateFile(id, file) { 25 | const { ctx } = this; 26 | return this.File.update(file, { where: { id } }); 27 | } 28 | async deleteFile(id) { 29 | const { ctx } = this; 30 | return this.File.destroy({ where: { id } }); 31 | } 32 | 33 | } 34 | 35 | module.exports = FileService; 36 | -------------------------------------------------------------------------------- /app/service/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const md5 = require('md5'); 4 | const { Service } = require('egg'); 5 | 6 | const users = [ 7 | { 8 | username: 'admin', 9 | password: md5('123456'), 10 | }, 11 | ]; 12 | 13 | class UserService extends Service { 14 | async find(username) { 15 | const user = await users.find(user => { 16 | return user.username === username; 17 | }); 18 | return user; 19 | } 20 | 21 | async login(username, password) { 22 | const { ctx } = this; 23 | console.log('session: ', ctx.session); 24 | if (ctx.session.username === username) { 25 | return true; 26 | } 27 | const user = await this.find(username); 28 | const result = user && (password === user.password); 29 | if (result) { 30 | ctx.session = user; 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | } 37 | 38 | module.exports = UserService; 39 | -------------------------------------------------------------------------------- /app/view/home.nj: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |