import { CryptoEngine, cipherKey, DBManager, DBModule, DBRemote, ModuleInstruction, ObjectData, parseXML, PrimitiveData, Scope, Structure, Variable, Expression, Data, Tag, Variables, FunctionData, BasicType, MapData, XmlError, data2boolean } from '@cyklang/core'; import { SigninResponse } from '@cyklang/core'; import loglevel from 'loglevel' import { useCykStore } from './cykStore'; import { Router } from 'vue-router'; import pako from 'pako' import { DirtyManager, VariableReact } from './cykReact'; import { fetchServer, originServer } from './utility'; import { JSCrypto } from './cykCrypto'; const logger = loglevel.getLogger('cykLang.ts') logger.setLevel('debug') export const structure = new Structure() // // function currentuser // returns {name, email, access} // structure.scope.addFunction('currentuser', async (params: Variables): Promise => { let authentication_required = true if (params.at(0)) { authentication_required = data2boolean(params.at(0)?.variable.data) } const store = useCykStore(); await store.currentuser(); const result = new ObjectData(structure.objectDataType, new Tag(''), structure.scope) const setUserField = (fieldName: 'user_id' | 'user_name' | 'user_email' | 'user_access' | 'user_company' | 'co_label') => { const value = store.user[fieldName]; let primitiveData: PrimitiveData if (fieldName === 'user_company' || fieldName === 'user_id') { primitiveData = new PrimitiveData(structure.numberDataType, value) } else { primitiveData = new PrimitiveData(structure.stringDataType, value) } result.variables.push(fieldName, new Variable(structure.stringDataType, primitiveData)) } setUserField('user_id') setUserField('user_name'); setUserField('user_email') setUserField('user_access') setUserField('user_company') setUserField('co_label') if ( ! store.user.logged && authentication_required ) { window.location.hash = '/login' throw 'Authentication Required' } return result }) // // function currentuseraccess // param: access to test // returns true if the current user has access to param // structure.scope.addFunction('currentuseraccess', async (params: Variables): Promise => { const varole = params.at(0) if (varole === undefined) throw 'currentuseraccess takes 1 argument: the role or skill you want to verify against current user' const role = varole?.variable.getString() if (role === undefined) throw 'currentuseraccess argument is undefined' let bool = false const user_access = useCykStore().user.user_access if (user_access !== undefined && user_access !== null) { const match = user_access.match(role) if (match !== null && match.length > 0) bool = true } let result = structure.falseData if (bool === true) result = structure.trueData return result }) const jsCrypto = new JSCrypto() export const dbRemote = new DBRemote(structure.scope, originServer(), jsCrypto) // export async function verifyDBSession(router: Router) { // } // export async function refreshDBSession(router: Router) { // return // } export let CURRENCY = 'EUR'; let dbManager: DBManager let dbManagerPromise: Promise | undefined export const getDBManager = async (): Promise => { try { if (dbManager === undefined) { dbManager = new DBManager(structure.scope, dbRemote) await dbManager.initialize() CURRENCY = await dbManager.getConfig('currency', 'EUR') || 'EUR' } } catch (err) { logger.error(err) } return dbManager // if (dbManagerPromise !== undefined) { // return dbManagerPromise // } // dbManagerPromise = new Promise(async (resolve, reject) => { // try { // await dbManager.initialize() // logger.debug('after dbManager.initialize()') // // useCykStore().dbManager = dbManager // resolve(dbManager) // } // catch (err) { // logger.error(err) // reject(err) // } // }) // return dbManagerPromise } // DBManager.initialize() calls DBRemote.connect() which downloads all the modules /modules (async () => { await getDBManager() })() /** * function devMode * if we are using Vite then the server is at localhost:3000 * @param url * @returns */ export const devMode = (url: string): string => { let result = url; if (process.env.DEV) { result = "//localhost:3000" + url; } return result; }; /** * * @param username * @param password * @param recaptcha_token */ async function signin_username(username: string, password: string, recaptchaToken: string) { const signinResponse = await dbRemote.signin_username(username, undefined, password, recaptchaToken ) // const signinResponse = await fetchServer("/api/signin_username", undefined, "post", // { user_name: username, user_password: password, recaptchaToken: recaptchaToken } // ) loadSigninResponse(signinResponse) } function loadSigninResponse(signinResponse: SigninResponse) { const store = useCykStore() store.user.user_name = signinResponse.content.user_name; store.user.user_email = signinResponse.content.user_email; store.user.user_access = signinResponse.content.user_access store.user.user_appli = signinResponse.content.user_appli store.user.user_lang = signinResponse.content.user_lang store.user.user_company = signinResponse.content.user_company store.user.accessJWT = signinResponse.accessJWT store.user.logged = true; window.sessionStorage.setItem('refreshJWT', signinResponse.refreshJWT) } /** * */ export function adminUser(): boolean { let result = false const store = useCykStore() if (store.user.user_access && store.user.user_access.indexOf("admin") !== -1) { result = true } return result } /** * */ async function signout() { const url = devMode("/api/signout") const store = useCykStore() store.loginRoute = undefined store.user.logged = false window.sessionStorage.removeItem('refreshJWT') await fetch(url) // await dbRemote.signout() } /** * */ async function saveSourceEdit() { const store = useCykStore() const dbManager = await getDBManager() if (store.module.source_edit === undefined) throw Error('saveSourceEdit store,module.source_edit undefined') const tag = parseXML('source_edit', store.module.source_edit) let dbname_edit = tag.attributes.DBNAME if (dbname_edit === undefined) throw Error(' missing dbname attribute') dbname_edit = dbname_edit.trim() if (dbname_edit === '') throw Error('dbname cannot be empty') const moduleExist = await selectFromTable('cyk_module', "module_dbname='" + dbname_edit + "'") const dbModule = new DBModule(dbname_edit) dbModule.source = store.module.source_edit if (store.module.module_id === undefined) { // new module // verify dbname is not in use if (moduleExist.length !== 0) { throw Error('dbname ' + dbname_edit + ' is already in use') } const id = await dbManager.dbModuleInsert(dbModule) alert('A new module has been inserted with module_id ' + id + ' and module_dbname ' + dbModule.dbname) store.module.module_id = id } else { // module update dbModule.id = store.module.module_id if (store.module.module_dbname !== dbname_edit) { // dbname has been changed if (moduleExist.length !== 0) { throw 'The module dbname ' + dbname_edit + ' is already in use by module_id ' + moduleExist[0].module_id } } // dbname can be already used by this module_id if (moduleExist.length > 0) { if (moduleExist[0].module_id !== store.module.module_id) { throw Error('dbname ' + dbname_edit + ' is already used by module_id ' + moduleExist[0].module_id) } } dbModule.description = store.module.module_description dbModule.access = store.module.module_access logger.debug('saveSourceEdit description ; ' + dbModule.description + ', access : ' + dbModule.access) await dbManager.dbModuleUpdate(dbModule) } store.module.module_source = store.module.source_edit } /** * * @param table * @param where * @param orderBy * @param fields * @returns */ export async function selectFromTable(table: string, where?: string, orderBy?: string, fields?: string): Promise { let result: any[] = [] let xml = ` ` if (fields !== undefined) { const __fields__ = fields.replace(/\s+/g, ',') xml += ` "${__fields__}"` } if (where !== undefined) { xml += ` ${where}` } if (orderBy !== undefined) { xml += ` '${orderBy}'` } xml += ` ` logger.trace('===== xml : \n' + xml) try { const scope = new Scope(structure, structure.scope, undefined) await executeXml(xml, scope) const objModule = scope.variables.getData('__module__') if (objModule !== undefined) { const objResult = (objModule).variables.getData('result') logger.trace('objResult : ') logger.trace(objResult) const objTable = objResult.variables.getData('resultset') result = arrayRecords(objTable, fields) } else { logger.debug('__module__ not found') } } catch (err) { logger.error(err) alert(err) throw err } return result } function arrayRecords(objectTable: ObjectData, fields?: string): any[] { logger.debug('(((((((((( arrayRecords ') logger.debug(objectTable) const result: any[] = [] for (let ind = 0; ind < objectTable.variables.length(); ind++) { const row = objectTable.variables.at(ind) if (row === undefined || row.variable.data === undefined || row.variable.data === null) continue if (row.variable.data.type.isPrimitive()) throw Error('arrayRecords objectData should be an array of ') const objData = row.variable.data const record: any = {} for (let indj = 0; indj < objData.variables.length(); indj++) { const namedVariable = objData.variables.at(indj) if (!namedVariable) continue if (namedVariable.name === undefined) continue if (fields !== undefined) { if ((' ' + fields + ' ').indexOf(' ' + name + ' ') === -1) continue } if (namedVariable.variable.data === null || namedVariable.variable.data === undefined) { record[namedVariable.name] = namedVariable.variable.data } else { record[namedVariable.name] = (namedVariable.variable.data as PrimitiveData).value } } result.push(record) } logger.trace('arrayRecords :') logger.trace(result) return result } async function executeXml(xml: string, scope: Scope): Promise { let result try { const tag = parseXML('__module__', xml) logger.trace('testXml dbname : ' + tag.attributes.DBNAME) // structure.setInstructionType("print", new PrintInstructionType(logfilename)) await getDBManager() const moduleInstructionType = structure.scope.getInstructionType('module') if (moduleInstructionType === undefined) { throw Error('moduleInstructionType not found') } const scopeParse = new Scope(scope.structure, scope, undefined) const moduleInst = await moduleInstructionType.parseInstruction(tag, scopeParse) //const scopeExecute = new Scope(scope.structure, scope, undefined) await moduleInst.execute(scope) result = moduleInst.dbModule // if (result === undefined) { // throw Error('moduleInst.dbModule undefined') // } } catch (err) { logger.error(err) throw err } return result } class Permissions { permiss: string constructor(permissions: string) { this.permiss = permissions.trim().toLowerCase() } check(required: string): boolean { let result if (this.permiss === '*' || this.permiss.indexOf(required) !== -1) result = true else result = false return result } } class AccessControl { groups: string permissions: Permissions constructor(groups: string, permissions: string) { this.groups = ' ' + groups.trim().toLowerCase() + ' ' this.permissions = new Permissions(permissions) } check(group: string, required: string) { let result = false if (this.groups.indexOf('*') !== -1 || this.groups.indexOf(' ' + group.trim().toLowerCase() + ' ') !== -1) { result = this.permissions.check(required) } return result } } class AccessControlList { list: AccessControl[] = [] constructor(ACL: string) { const items = ACL.split(';') items.forEach((item_) => { const item = item_.trim().toLowerCase() if (item !== '') { if (item.indexOf(':') === -1) throw 'AccessControlList format is group:permissions' const [groups, permissions] = item.split(':') this.list.push(new AccessControl(groups, permissions)) } }) } check(userGroups: string, required: string): boolean { let result = false const groups = userGroups.split(' ') groups.forEach((group) => { this.list.forEach((accessControl) => { result = result || accessControl.check(group, required) }) }) return result } } /** * * @param userGroups * @param resourceACL * @param resourceName * @param accessRequired * @returns */ export function verifyAccessRight(userGroups: string, resourceACL: string, resourceName: string, accessRequired: string) { if (accessRequired === undefined || accessRequired === null || accessRequired.trim() === '') return if (resourceACL === undefined || resourceACL === null || resourceACL.trim() === '') return // if (userGroups === undefined || userGroups === null || userGroups.trim() === '') throw 'Authorization required : ' + resourceName + ', your user group is undefined' const ACL = new AccessControlList(resourceACL) if (ACL.check(userGroups, accessRequired) === false) { throw ('Authorization required : ') + resourceName } } export function getVarValue( variable: Variable | undefined ): string | number | null | undefined { let result: string | number | null | undefined; if ( variable !== undefined && variable.data !== null && variable.data !== undefined ) { const pvalue = variable.getPrimitiveValue(); if (variable.dataType.name === 'datetime') { if (pvalue instanceof Date) { logger.debug('result instanceof Date'); //logger.debug(variable.data); logger.debug(pvalue.toISOString()); result = pvalue.toISOString().slice(0, 10); logger.debug('>> get date ' + result); } else { logger.debug('>> get date ' + result); result = ''; } } else if (variable.dataType.name === 'boolean') { result = pvalue === true ? 1 : 0; } else if (variable.dataType.name === 'number') { result = pvalue; } else if (variable.dataType.name === 'string') { result = pvalue; } } else if ( variable !== undefined && (variable.data == undefined || variable.data === null) ) { result = variable.data; } return result; } /** * * @param value * @returns */ export function toNumber(value: BasicType): number | undefined { if (typeof value === 'number') { return value; } if (typeof value === 'string') { const num = Number(value.replace(',', '.')) return !isNaN(num) && value.trim() !== '' ? num : undefined; } return undefined; } /** * * @param variable * @param nval */ export function setVarValue( variable: Variable, nval: string | number | null | undefined ) { const dataTypeName = variable.dataType.name; let ndata; if (dataTypeName === 'string') { const strin = String(nval); ndata = new PrimitiveData( structure.stringDataType, strin ); variable.setValue(ndata); } else if (dataTypeName === 'number') { const numericValue = toNumber(nval) if (numericValue) { ndata = new PrimitiveData(structure.numberDataType, numericValue) } else { ndata = undefined } variable.setValue(ndata) } else if (dataTypeName === 'boolean') { const bool = Boolean(nval); ndata = new PrimitiveData( structure.booleanDataType, bool ); variable.setValue(ndata); } else if (dataTypeName === 'datetime') { let dat; if (nval === null) { logger.debug('>> set date null'); dat = null; } else if (nval === undefined) { logger.debug('>> set date undefined'); dat = null; } else if (nval === '') { logger.debug('>> set date blank'); dat = null; } else { logger.debug('>> set date ' + nval); dat = new Date(String(nval)); } logger.debug('set date ' + dat); ndata = new PrimitiveData(structure.datetimeDataType, dat); variable.setValue(ndata); } } export function useCykLang() { return { structure, dbRemote, // verifyDBSession, // refreshDBSession, getDBManager, signin_username, signout, saveSourceEdit, // selectFromTable, arrayRecords, verifyAccessRight, } } export class ComponentModel { objectData: ObjectData | undefined; model: Variable | undefined; variableReact: VariableReact | undefined; dirtyManager?: DirtyManager | undefined; attributes: { [key: string]: BasicType } = {} // after interpolating objectData.tag.attributes // parent: ComponentModel | undefined // children: ComponentModel[] = [] validationFunction: ValidationFunction | undefined validationFunctionSetter: ValidationFunctionSetter | undefined promise: Promise | undefined resolve: ((value: void | PromiseLike) => void) | undefined constructor(objectData: ObjectData | undefined, model: Variable | undefined, variableReact?: VariableReact, dirtyManager?: DirtyManager) { this.objectData = objectData; this.model = model; this.variableReact = variableReact this.dirtyManager = dirtyManager // this.parent = parent // if (parent !== undefined) { // parent.children.push(this) // } } async interpolateAttributes() { if (!this.objectData) return const attrib = this.objectData?.tag.attributes try { const express = new Expression(this.objectData.scope) for (const key in attrib) { const value = attrib[key] if (key.startsWith('_')) { const data = await express.evaluate(value) logger.debug(`interpolateAttributes() key: ${key}, value: ${value}`) if (data !== undefined && data !== null) { if (! data.type.isPrimitive()) { throw `interpolateAttributes() key: ${key}, value: ${value} is not primitive but ${data.type.name}` } else { this.attributes[key] = (data as PrimitiveData).value } } } else { this.attributes[key] = await express.substitute(value) } } } catch (err) { logger.debug('interpolateAttributes() attrib:', attrib) logger.error(err) throw new XmlError(String(err), this.objectData?.tag) } // for (let ind = 0; ind < ATTS.length; ind++) { // const ATT = ATTS[ind] // const value = this.objectData?.tag.attributes[ATT] // if (this.objectData && value !== undefined) { // const express = new Expression(this.objectData.scope) // this.attributes[ATT] = await express.substitute(value) // } // } } } export type ValidationFunction = () => Promise export type ValidationFunctionSetter = (validationFunction: ValidationFunction) => void; export type PushModalDialogFunction = (dialogComponent: ComponentModel) => Promise export type PopModalDialogFunction = () => Promise; /** * class ChangeWorker * receive change events and trigger onchangeFunction */ export class ChangeWorker { onchangeFunction: FunctionData callerScope: Scope scheduled: boolean = false isRunning: boolean = false constructor(onchangeFunction: FunctionData, callerScope: Scope) { this.onchangeFunction = onchangeFunction this.callerScope = callerScope } // method onchangeRequired // called by onchange onchangeRequired() { if (this.isRunning) { this.scheduled = true } else { this.execute() } } execute() { // logger.debug('ChangeWorker.execute() BEGIN') this.isRunning = true this.onchangeFunction.callFunction(new Variables(), this.callerScope).then(() => { }).catch((err) => { logger.error(err) throw err }).finally(() => { this.isRunning = false if (this.scheduled) { this.scheduled = false this.execute() } }) } } /** * * @param object * @param name * @param defaultValue * @returns */ export function objectParameter(object: ObjectData, name: string, defaultValue: type): type { let result: type | undefined; const variable = object.variables.getVariable(name) const attribute = object.tag.attributes[name.toUpperCase()] if (variable && variable.data !== undefined) { if (!variable.dataType.isPrimitive()) throw 'objectParameter is not primitive: ' + name result = (variable.data as PrimitiveData).value as type } if (result === undefined && attribute !== undefined) { result = attribute } if (result === undefined) { result = defaultValue } return result } /** * * @param variable */ export function buildComponentModel(variable: Variable): ComponentModel { let result: ComponentModel; result = new ComponentModel( undefined, variable, ); return result; }; /** * * @param text * @returns */ export function xlate(text: string) { return structure.scope.xlate(text) } /** * * @param module */ export async function moduleExist(module: string): Promise { let result: DBModule | undefined try { // await verifyDBSession(router); const dbManager = await getDBManager(); result = await dbManager.dbModuleExist(module); } catch (err) { // logger.debug('moduleExist exception', err) logger.error(err) } return result } /** * * @param module * @returns */ export async function fetchModule(module: string): Promise { let result; try { const dbModule = await moduleExist(module) if (dbModule === undefined) { logger.error('--- fetchModule module ' + module + ' not found') throw '---- fetchModule module ' + module + ' not found'; } if (dbModule.auth) { const store = useCykStore() if (!store.user.logged) { window.location.hash = '/login' throw 'Authentication Required' } verifyAccessRight( String(store.user.user_access), dbModule.access || '', 'Exec Permission on Module ' + module, 'x' ); } result = dbModule.source; // logger.debug('fetchModule.complete '+module) } catch (err) { // logger.debug('fetchModule exception', err) logger.error(err); throw err; } finally { // $q.loading.hide(); } return result; };