/* global ubs_report ncrc32 */
// eslint-disable-next-line camelcase
const me = ubs_report
const fs = require('fs')
const path = require('path')
const _ = require('lodash')
const FileBasedStoreLoader = require('@unitybase/base').FileBasedStoreLoader
const csShared = require('@unitybase/cs-shared')
const UBDomain = csShared.UBDomain
const LocalDataStore = csShared.LocalDataStore
const UB = require('@unitybase/ub')
const App = UB.App
const mStorage = UB.mixins.mStorage
me.entity.addMethod('select')
me.entity.addMethod('update')
me.entity.addMethod('insert')
me.entity.addMethod('addnew')
if (process.isDebug) {
me.entity.addMethod('testServerRendering')
}
// here we store loaded reports
let resultDataCache = null
let modelLoadDate
const TEMPLATE_CONTENT_TYPE = 'application/ubreport'
const SCRIPT_CONTENT_TYPE = 'application/def'
const REL_PATH_TAIL = 'reports'
const TEMPLATE_EXTENSION = '.template'
const SCRIPT_EXTENSION = '.js'
const REPORT_BODY_TPL = `
exports.reportCode = {
/**
* Generate report data and render report. Function should:
* - prepare reportData - a JavaScript object passed to mustache template
* - call this.buildHTML(reportData) to render mustache template
* - optionally call this.transformToPdf(htmlReport) where htmlReport is HTML from prev. step
* - for server side returned value should be string, for client - Promise resolved to string
*
* @cfg {function} buildReport
* @params {[]|{}} reportParams
* @returns {Promise|Object}
*/
buildReport: function(reportParams){
var reportData = this.buildHTML(reportParams)
if (this.reportType === 'pdf') {
result = this.transformToPdf(reportData)
}
return result
},
/** optional report click event handler
* see click)sample report inside UBS model
*/
// onReportClick: function (e) {
// // prevent default action
// e.preventDefault()
// // get table/cell/roe based on event target
// let cellInfo = UBS.UBReport.cellInfo(e)
// ...
// }
}
`
/**
* Check integrity of file content. Passed as a callback to FileBasedStore.onBeforeRowAdd
* @private
* @param {FileBasedStoreLoader} loader
* @param {String} fullFilePath
* @param {String} content
* @param {Object} row
* @return {boolean}
*/
function postProcessing (loader, fullFilePath, content, row) {
// we fill relPath in form "modelName"|"path inside model public folder" as expected by mdb virtual store
let relPath = loader.processingRootFolder.model.name + '|' + REL_PATH_TAIL
// fill model attribute by current folder model name
row.model = loader.processingRootFolder.model.name
// fill name attribute with file name w/o ".xml" extension
let fileName = path.basename(fullFilePath)
row.report_code = fileName.substring(0, fileName.length - TEMPLATE_EXTENSION.length)
if (row.ID) console.warn(`Please, remove a row "<!--@ID "${row.ID}"-->" from a file ${fileName}. In UB4 report ID is generated automatically as crc32(fileNameWoExtension)`)
row.ID = ncrc32(0, row.report_code)
// fill formDef attribute value
row.template = JSON.stringify({
fName: fileName,
origName: fileName,
ct: TEMPLATE_CONTENT_TYPE,
size: content.length,
md5: 'fakemd50000000000000000000000000',
relPath: relPath
})
fileName = fileName.substring(0, fileName.length - TEMPLATE_EXTENSION.length) + SCRIPT_EXTENSION
let jsFilePath = path.join(path.dirname(fullFilePath), fileName)
if (fs.existsSync(jsFilePath)) {
let jsFileStat = fs.statSync(jsFilePath)
row.code = JSON.stringify({
fName: fileName,
origName: fileName,
ct: SCRIPT_CONTENT_TYPE,
size: jsFileStat.size,
md5: 'fakemd50000000000000000000000000',
relPath: relPath
})
// check js file modification and if later when def file - replace mi_modifyDate
if (loader.haveModifyDate && row.mi_modifyDate < jsFileStat.mtime) {
row.mi_modifyDate = jsFileStat.mtime
}
}
return true
}
function loadAll () {
const models = App.domainInfo.models
let folders = []
let modelLastDate = new Date(App.globalCacheGet('UB_STATIC.modelsModifyDate')).getTime()
console.debug('modelLastDate = ', modelLastDate)
if (!resultDataCache || modelLoadDate < modelLastDate) {
console.debug('load reports from models directory structure')
for (let modelCode in models) {
let model = models[modelCode]
let mPath = path.join(model.realPublicPath, REL_PATH_TAIL)
folders.push({
path: mPath,
model: model // used for fill Document content for `mdb` store in postProcessing
})
}
let loader = new FileBasedStoreLoader({
entity: me.entity,
foldersConfig: folders,
fileMask: new RegExp(TEMPLATE_EXTENSION + '$'),
attributeRegExpString: FileBasedStoreLoader.XML_ATTRIBURE_REGEXP,
onBeforeRowAdd: postProcessing
})
resultDataCache = loader.load()
modelLoadDate = modelLastDate
} else {
console.debug('ubs_report: resultDataCache already loaded')
}
return resultDataCache
}
/**
* Retrieve data from resultDataCache and init ctxt.dataStore.
* Caller MUST set dataStore.currentDataName before call doSelect function
* @private
* @param {ubMethodParams} ctxt
*/
function doSelect (ctxt) {
const cType = ctxt.dataStore.entity.cacheType
let mP = ctxt.mParams
let aID = mP.ID
let cachedData = loadAll()
if (!(aID && (aID > -1)) && (cType === UBDomain.EntityCacheTypes.Entity || cType === UBDomain.EntityCacheTypes.Entity) && (!mP.skipCache)) {
let reqVersion = mP.version
mP.version = resultDataCache.version
if (reqVersion === resultDataCache.version) {
mP.resultData = {}
mP.resultData.notModified = true
return
}
}
let filteredData = LocalDataStore.doFilterAndSort(cachedData, mP)
// return as asked in fieldList using compact format {fieldCount: 2, rowCount: 2, values: ["ID", "name", 1, "ss", 2, "dfd"]}
let resp = LocalDataStore.flatten(mP.fieldList, filteredData.resultData)
ctxt.dataStore.initialize(resp)
}
/**
* @method select
* @memberOf ubs_report_ns.prototype
* @memberOfModule @unitybase/ubs
* @published
* @param {ubMethodParams} ctx
* @param {UBQL} ctx.mParams ORM query in UBQL format
* @return {Boolean}
*/
me.select = function (ctx) {
ctx.dataStore.currentDataName = 'select'
doSelect(ctx)
return true // everything is OK
}
/**
* Check model exists
* @private
* @param {string} reportCode
* @param {String} modelName
*/
function validateInput (reportCode, modelName) {
let model = App.domainInfo.models[modelName]
if (!model) {
throw new Error(`ubs_report: Invalid model attribute value "${modelName}". Model not exist in domain`)
}
if (!reportCode) {
throw new Error('Parameter "report_code" not passed in execParams')
}
}
/**
* @private
* @param {ubMethodParams} ctxt
* @param {Object} storedValue
* @param {Boolean} isInsert
* @return {boolean}
*/
function doUpdateInsert (ctxt, storedValue, isInsert) {
console.debug('--==== ubs_report.doUpdateInsert ===-')
let entity = me.entity
let mP = ctxt.mParams
let newValues = mP.execParams || {}
let ID = newValues.ID
// move all attributes from execParams to storedValue
_.forEach(newValues, function (val, key) {
let attr = entity.attributes[key]
if (attr && (attr.dataType !== UBDomain.ubDataTypes.Document)) {
storedValue[key] = val
}
})
let newTemplateInfo = newValues.template
let reportBody
if (isInsert || !newTemplateInfo) {
reportBody = ''
} else {
reportBody = App.blobStores.getContent(
{
entity: entity.name,
attribute: 'template',
ID: ID,
isDirty: Boolean(newTemplateInfo)
},
{encoding: 'utf-8'}
)
let clearAttrReg = new RegExp(FileBasedStoreLoader.XML_ATTRIBURE_REGEXP, 'gm') // seek for <!--@attr "bla bla"-->CRLF
reportBody = reportBody.replace(clearAttrReg, '') // remove all old entity attributes
let attributes = entity.attributes
for (let attrName in attributes) {
let attr = attributes[attrName]
if (attr.dataType !== UBDomain.ubDataTypes.Document && (attr.defaultView || attrName === 'ID')) {
reportBody = '<!--@' + attrName + ' "' + storedValue[attrName] + '"-->\r\n' + reportBody
}
}
}
let docInfo = App.blobStores.putContent({
entity: entity.name,
attribute: 'template',
ID: ID,
fileName: storedValue.report_code + TEMPLATE_EXTENSION,
ct: TEMPLATE_CONTENT_TYPE
}, reportBody)
// add a relPath
docInfo.relPath = storedValue.model + '|' + REL_PATH_TAIL
// and update an attribute value to the new blob info
storedValue.template = JSON.stringify(docInfo)
if (isInsert) {
let reportCodeInfo = App.blobStores.putContent({
entity: entity.name,
attribute: 'code',
ID: ID,
fileName: storedValue.report_code + SCRIPT_EXTENSION
}, REPORT_BODY_TPL)
newValues.code = JSON.stringify(reportCodeInfo)
}
let newCode = newValues.code
if (newCode) { // in case report script is modified add a relPath
let parsed = JSON.parse(newCode)
parsed.relPath = storedValue.model + '|' + REL_PATH_TAIL
parsed.fName = storedValue.report_code + SCRIPT_EXTENSION
parsed.ct = SCRIPT_CONTENT_TYPE
storedValue.code = JSON.stringify(parsed)
} else {
delete storedValue.code
}
// commit BLOB store changes
let fakeCtx = {
dataStore: null,
mParams: {
execParams: storedValue
}
}
ctxt.dataStore.commitBLOBStores(fakeCtx, isInsert === false)
ctxt.dataStore.initialize([storedValue])
resultDataCache = null // drop cache. afterInsert call select and restore cache
return true
}
/**
* @method update
* @memberOf ubs_report_ns.prototype
* @memberOfModule @unitybase/ubs
* @published
* @param {ubMethodParams} ctxt
* @return {boolean}
*/
me.update = function (ctxt) {
let inParams = ctxt.mParams.execParams || {}
let ID = inParams.ID
let cachedData = loadAll()
let storedValue = LocalDataStore.byID(cachedData, ID)
if (storedValue.total !== 1) {
throw new Error(`Record with ID=${ID} not found`)
}
storedValue = LocalDataStore.selectResultToArrayOfObjects(storedValue)[0]
validateInput(inParams.report_code || storedValue.report_code, inParams.model || storedValue.model)
doUpdateInsert(ctxt, storedValue, false)
return true // everything is OK
}
/**
* Check ID is unique and perform insertion
* @method insert
* @memberOf ubs_report_ns.prototype
* @memberOfModule @unitybase/ubs
* @published
* @param {ubMethodParams} ctxt
* @return {boolean}
*/
me.insert = function (ctxt) {
let inParams = ctxt.mParams.execParams
let cachedData = loadAll()
let newReportCode = inParams.report_code
let ID = ncrc32(0, newReportCode)
inParams.ID = ID
validateInput(inParams.report_code, inParams.model)
let row = LocalDataStore.byID(cachedData, ID)
if (row.total) {
throw new Error(`<<<Report with ID ${ID} already exist>>>`)
}
let oldValue = {}
doUpdateInsert(ctxt, oldValue, true)
return true
}
const mime = require('mime-types')
if (process.isDebug) {
/**
* REST endpoint for Report test purpose. Available in `-dev` mode only.
* Expect POST request with JSON on body {reportCode: 'reportCode', responseType: 'pdf'|'html', reportParams: {paramName: paramValue, ...}}
* Return a HTML/PDF
* @param {null} ctxt
* @param {THTTPRequest} req
* @param {THTTPResponse} resp
*/
me.testServerRendering = function (ctxt, req, resp) {
let body = req.read()
let params = JSON.parse(body)
const UBServerReport = require('./modules/UBServerReport')
let result = UBServerReport.makeReport(params.reportCode, params.responseType, params.reportParams)
if (result.reportType === 'pdf') {
console.debug('Generate a PDF report of size=', result.reportData.byteLength)
} else {
console.debug('Generate a HTML report of size=', result.reportData.length)
}
resp.writeEnd(result.reportData)
resp.writeHead('Content-Type: ' + mime.lookup(result.reportType))
resp.statusCode = 200
}
}
/**
* New report
* @method addNew
* @memberOf ubm_form_ns.prototype
* @memberOfModule @unitybase/ubm
* @published
* @param {ubMethodParams} ctxt
* @return {boolean}
*/
me.addnew = mStorage.addNew