UNPKG

cmsmon

Version:
318 lines (286 loc) 12.5 kB
'use strict'; const path = require('path'); const unless = require('express-unless'); const cheerio = require('cheerio'); const _ = require('lodash'); require('generator-bind').polyfill(); const JsonFn = require('json-fn'); const autopopulate = require('mongoose-autopopulate'); const traverse = require('traverse'); module.exports = (cms) => { const {app, Q} = cms; app.get('/cms-types', function*(req, res) { res.send(_.map(cms.Types, (v, type) => ({type}))); }) app.post('/cms-types/:type/:id/:fn', function*(req, res) { const {type, id, fn} = req.params; const args = _.map(JsonFn.clone(req.body, true), v => v); const {Model, serverFn} = cms.Types[type]; const obj = yield Model.findById(id).exec(); const result = obj ? yield* serverFn[fn].bind(obj)(...args) : yield* serverFn[fn](...args); res.send(isNaN(result) ? result : result + ''); }) app.delete('/cms-types/:type', function*(req, res) { const {type} = req.params; const {Model} = cms.Types[type]; Model.remove({}); const result = yield Model.remove({}).exec(); res.send(result); }) app.post('/cms-types/:type', function*(req, res) { const withTemplate = req.query.template === 'true'; const noElement = req.query.element === 'false'; const ref = req.query.element; const content = req.body; const {type} = req.params; const {Model, Formatter, FormatterUrl, Form, info, fn, serverFnForClient} = cms.Types[type]; let obj = noElement ? new Model(content) : (ref ? yield Model.findOne({_id: ref}) : yield Model.create(content)); try { var _autopopulate = Model.schema.s.hooks._pres.find[0].fn; if (_autopopulate) { const _query = { p: obj, populate: function (opt) { this.p = this.p.populate(opt); } } _autopopulate.bind(_query)(); yield _query.p.execPopulate(); } } catch (e) { } let result = {info, fn, serverFn: serverFnForClient}; result.data = obj; if (withTemplate) { result.form = Form; const $ = cheerio.load(Formatter ? Formatter : cms.compile(FormatterUrl)(obj)); cms.filters.element.forEach((fn) => fn($, obj)); result.template = $.html(); result.fn = fn; result.serverFn = serverFnForClient; result.info = info; } res.send(JsonFn.stringify(result)); }) function registerSchema(schema, options) { const { name, label, formatter, formatterUrl, initSchema, title, fn = {}, serverFn = {}, tabs, isViewElement = true, mTemplate, admin = {query: []}, alwaysLoad = false, restifyOptions, info = {}, controller, lean, link } = options; cms.filters.schema.forEach((fn) => fn(schema, name)); if (!(schema instanceof cms.mongoose.Schema)) { schema = new cms.mongoose.Schema(schema, {toObject: {virtuals: true}, toJSON: {virtuals: true}}); } if (options.autopopulate) schema.plugin(autopopulate); schema.add({_textIndex: {type: String, form: false, index: 'text'}}); schema.pre('findOneAndUpdate', function (next) { let _textIndex = '' traverse(this._update).forEach(function (node) { if (!node) return; if (this.key && !_.includes(['$set', '$setOnInsert', '__v', '_id', 'id'], this.key)) { const _type = schema.path(this.path.filter(p=> p !== '$set' && p !== '$setOnInsert').join('.')); if (_type) { const type = _type.instance; if (type === 'ObjectID') { this.block(); _textIndex += node[cms.Types[_type.options.ref].info.title] + ' '; } else if (type === 'Number') { _textIndex += node + ' '; } else if (type === 'String') { _textIndex += node + ' '; } } else { this.block(); } } else if (this.key) { this.block(); } }) this._update._textIndex = _textIndex; next(); }); // schema.index({'$**': 'text'}); if (initSchema) initSchema(schema); const Model = cms.mongoose.model(name, schema); cms.restify.serve(app, Model, _.assign(restifyOptions, {lean: false})); _.merge(fn, cms.filters.fn); _.merge(serverFn, cms.filters.serverFn); cms.Types[name] = { schema, Model, label, clear() { this.Form = null; this.Paths = null; this.Queries = null; }, Formatter: formatter, FormatterUrl: formatterUrl, info: _.assign({ title, isViewElement, admin, alwaysLoad }, info), fn, serverFn, controller, link, get serverFnForClient() { if (!this._serverFnForClient) { this._serverFnForClient = {}; _.each(serverFn, (fn, k) => { this._serverFnForClient[k] = function (post, scope, type, fnName) { const model = this; if (!scope.serverFnData) scope.serverFnData = []; scope.serverFn[fnName] = function () { const getFnData = args => _.find(scope.serverFnData, v => JSON.stringify({args: v.args, k: v.k}) === JSON.stringify({args, k: fnName})); const data = getFnData(arguments); if (data && data.result) { return data.result; } if (!data) { scope.serverFnData.push({args: arguments, k: fnName}); const args = arguments; post(`/cms-types/${type}/${model._id}/${fnName}`, arguments).then(res => getFnData(args).result = res.data) return scope.serverFnData.length - 1; } }; } }) } return this._serverFnForClient; }, mTemplate, lean, get webType() { if (!this.Form) { _.assign(this, cms.utils.initType(schema, tabs, name)); } return { template: this.template, label: this.label, form: this.Form, tabs: tabs, queries: this.Queries, paths: this.Paths, list: [], info: this.info, fn: this.fn, serverFn: this.serverFnForClient, columns: _.map(_.pickBy(this.Model.schema.paths, k =>['id', '_id', '__v', '_textIndex'].indexOf(k) === -1, true), (v, k) => { return v.options && v.options.label ? v.options.label : k; }), store: this.store, controller: this.controller, lean: this.lean, link: this.link } }, getWebTypeWithData: function*() { const Type = this.webType; Type.list = yield this.Model.find({}); return Type; }, get template() { if (!this.Formatter && !this.FormatterUrl) return ''; return this.Formatter ? this.Formatter : cms.compile(this.FormatterUrl)(); } }; // todo: serverFn return Model; } cms.filters.serverFn.link = function*(src) { if (!src) return ''; if (src.indexOf('http://') !== -1) return src; return `${cms.data.baseUrlPath}${src[0] === '/' ? '' : '/'}${src}`; } cms.filters.fn.getWebStyles = function (model) { const _styles = {} if (model) { _.each(model.styles, style => _styles[style.choice] = style[style.choice]) } else { _.each(this.styles, style => _styles[style.choice] = style[style.choice]) } return _styles; } cms.filters.fn.getStyles = function (model) { let styles = ''; try { const _styles = {} if (model) { model.styles.forEach(style => _styles[style.choice] = style[style.choice]) } else { this.styles.forEach(style => _styles[style.choice] = style[style.choice]) } _.each(_styles, (v, k) => { styles += `${_.kebabCase(k)}:${v};`; }) } catch (e) { } return styles; } cms.registerSchema = registerSchema; // websocket cms.io.on(`connection`, function (socket) { socket.on('error', function (e) { console.warn(e); }) socket.on('message', function*({path, params = {}, uuid, model}) { const base = '([^\/]*)\/api\/v1\/([^\/]*)'; const modelQueryTester = new RegExp(`${base}$`); const countQueryTester = new RegExp(`${base}\/count$`); if (modelQueryTester.test(path)) { const [,method,type] = path.match(modelQueryTester); if (method === 'get') { if (Object.keys(cms.Types).indexOf(type) !== -1) { let q = cms.getModel(type).find(params.query); if (params.populate) { q = q.populate(params.populate); } q = q.sort(params.sort).skip(params.skip).limit(params.limit); if (params.lean) q = q.lean(); const result = yield q; socket.emit('message', {result, uuid}); } } else if (method === 'post') { if (Object.keys(cms.Types).indexOf(type) !== -1) { var Model = cms.Types[type].Model; if (!model._id) { const _model = new Model(); model._id = _model._id; } yield Model.findByIdAndUpdate(model._id, _.pickBy(model, (v, k) => k !== '__v', true), { upsert: true, setDefaultsOnInsert: true }).exec(); let result = yield Model.findById(model._id); socket.emit('message', {result, uuid}); } } } if (countQueryTester.test(path)) { const [,method,modelName] = path.match(countQueryTester); if (method === 'get') { if (Object.keys(cms.Types).indexOf(modelName) !== -1) { const result = yield cms.Types[modelName].Model.find(params.query).count(params.query); socket.emit('message', {result, uuid}); } } } var serverFnPath = /\/cms-types\/([^\/]*)\/([^\/]*)\/([^\/]*)/; if (serverFnPath.test(path)) { const [type,id,fn] = path.match(serverFnPath); const args = params; const {Model, serverFn} = cms.Types[type]; const obj = yield Model.findById(id).exec(); const result = yield* serverFn[fn].bind(obj)(...args); socket.emit('message', {result: isNaN(result) ? result : result + '', uuid}); } }); }); }