diff --git a/packages/enketo-express/app/controllers/survey-controller.js b/packages/enketo-express/app/controllers/survey-controller.js index f0867c3524875db5046b44641493bdbf491bc2c0..b1cd40cbf9458848d17415dced30f8a642f32c0a 100644 --- a/packages/enketo-express/app/controllers/survey-controller.js +++ b/packages/enketo-express/app/controllers/survey-controller.js @@ -188,8 +188,8 @@ function edit(req, res, next) { */ function _renderWebform(req, res, next, options) { const deviceId = - req.signedCookies["__enketo_meta_deviceid"] || - `${req.hostname}:${utils.randomString(16)}`, + req.signedCookies["__enketo_meta_deviceid"] || + `${req.hostname}:${utils.randomString(16)}`, cookieOptions = { signed: true, maxAge: 10 * 365 * 24 * 60 * 60 * 1000, @@ -201,8 +201,10 @@ function _renderWebform(req, res, next, options) { options.formId = req?.query?.id; options.backendToken = req?.query?.token; options.formSpec = req?.query?.formSpec; + options.userId = req?.query?.userId; res.cookie("backendToken", req?.query?.token, cookieOptions); + res.cookie("userId", req?.query?.userId, cookieOptions); res.cookie("enketoFormId", req?.query?.id, cookieOptions); res.cookie("__enketo_meta_deviceid", deviceId, cookieOptions).render( "surveys/webform", diff --git a/packages/enketo-express/app/views/layout.pug b/packages/enketo-express/app/views/layout.pug index 218ee0cfc23855726a07d0c1a456adb4785bd212..86f4cdf40590d44cbe259759e791fdd806a34224 100644 --- a/packages/enketo-express/app/views/layout.pug +++ b/packages/enketo-express/app/views/layout.pug @@ -6,6 +6,7 @@ html(lang=language, dir=dir(language)) meta(charset="utf-8") meta(name="author", content="Martijn van de Rijdt (Enketo LLC)") meta(name="formId" content=formId) + meta(name="userId" content=userId) meta(name="backendToken" content=backendToken) meta(name="formSpec" content=formSpec) if !desktop diff --git a/packages/enketo-express/package.json b/packages/enketo-express/package.json index 86378398d27e79792874f757a5afe08efd561c87..183357321cce60bae3b03b8be7b49c6852653d73 100644 --- a/packages/enketo-express/package.json +++ b/packages/enketo-express/package.json @@ -42,9 +42,11 @@ "basic-auth": "^2.0.1", "body-parser": "^1.19.0", "bristol": "^0.4.0", + "browser-image-compression": "^2.0.2", "busboy": "^0.3.1", "compression": "^1.7.4", "cookie-parser": "^1.4.5", + "cron": "^2.2.0", "csurf": "^1.11.0", "db.js": "^0.15.0", "debug": "^4.3.2", @@ -68,6 +70,7 @@ "jwt-simple": "^0.5.6", "libxslt": "^0.10.1", "load-grunt-tasks": "^5.1.0", + "localforage": "^1.10.0", "lodash": "^4.17.21", "morgan": "^1.10.0", "node-fetch": "^3.2.10", diff --git a/packages/enketo-express/public/js/src/module/controller-webform.js b/packages/enketo-express/public/js/src/module/controller-webform.js index 9bfa32149851b90d05957bde6153eeea5c9d124e..d4f0072d7cfbc9ade3b1650691e33a0bb72dd4bd 100644 --- a/packages/enketo-express/public/js/src/module/controller-webform.js +++ b/packages/enketo-express/public/js/src/module/controller-webform.js @@ -26,6 +26,10 @@ const cronInterval = setInterval(async () => { }) }, 5000); +function getMeta(metaName) { + return document.querySelector(`meta[name=${metaName}]`).content; +} + /** * @typedef {import('../../../../app/models/survey-model').SurveyObject} Survey */ @@ -224,7 +228,6 @@ function _resetForm(survey, options = {}) { } }); } - /** * Loads a record from storage * @@ -703,6 +706,14 @@ function _setEventHandlers(survey) { } }); + document.addEventListener(events.Edited().type, async event => { + const formController = new FormController({}); + const userId = getMeta('userId'); + const keyToStorage = `${userId}_${form.model.rootElement.id}_${new Date().toISOString().split("T")[0]}`; + let olderFiles = JSON.parse(localStorage.getItem(keyToStorage)) || {}; + await formController.broadcastFormDataUpdate(form.getDataStr(), {}); + }); + if (inIframe() && settings.parentWindowOrigin) { document.addEventListener(events.SubmissionSuccess().type, postEventAsMessageToParentWindow); document.addEventListener(events.Edited().type, postEventAsMessageToParentWindow); @@ -730,39 +741,89 @@ function _setEventHandlers(survey) { }); } - let arrayOfFileURLs = [] - document.addEventListener(events.XFormsValueChanged().type, async () => { + + document.addEventListener(events.XFormsValueChanged().type, async (e) => { const formController = new FormController({}); - const formFiles = await fileManager.getCurrentFiles(); - if (formFiles) { - console.log(arrayOfFileURLs?.length) - console.log("formFiles: " + formFiles?.length) - if (arrayOfFileURLs?.length <= formFiles?.length && formFiles?.length) { - for (let i = 0; i < formFiles.length; i++) { - const file = formFiles[i]; - if (typeof file === 'object') { - const fileURL = await formController.uploadFile(file); - if (fileURL) - arrayOfFileURLs.push({ url: fileURL, name: file.name }); + const userId = getMeta('userId'); + const keyToStorage = `${userId}_${form.model.rootElement.id}_${new Date().toISOString().split("T")[0]}`; + let olderFiles = JSON.parse(localStorage.getItem(keyToStorage)) || {}; + await formController.broadcastFormDataUpdate(form.getDataStr(), {}); + let arrayOfFileURLs = {}; + if (e.target.nodeName === "INPUT" && e.target.accept === "image/*") { + const formFiles = await fileManager.getCurrentFiles(); + if (formFiles) { + console.log("formFiles: " + formFiles?.length) + if (formFiles?.length) { + for (let i = 0; i < formFiles.length; i++) { + const file = formFiles[i]; + const parts = e.target.name.split("/").slice(); + const lastPart = parts[parts.length - 1] + // if (typeof file === 'object' && file.name.includes(lastPart)) { + if (typeof file === 'object') { + const fileURL = await formController.uploadFile(file); + if (fileURL) { + console.log({ fileURL }); + arrayOfFileURLs[e.target.name] = { url: fileURL, name: file.name, ref: e.target.name }; + // Broadcast File Remove + olderFiles = JSON.parse(localStorage.getItem(keyToStorage)) || {}; + const arrayOfFileURLsNew = { ...olderFiles, ...arrayOfFileURLs }; + for (const [key, value] of Object.entries(olderFiles)) { + if (arrayOfFileURLs[key] !== undefined && value.url !== '') { + arrayOfFileURLsNew[key] = value; + } + } + if (olderFiles && olderFiles !== undefined) { + for (const [key, value] of Object.entries(olderFiles)) { + if (arrayOfFileURLsNew[key] === undefined) { + arrayOfFileURLsNew[key] = { url: '', name: '', ref: '' } + } + } + } + localStorage.setItem(keyToStorage, JSON.stringify(arrayOfFileURLsNew)); + await formController.broadcastFormDataUpdate(form.getDataStr(), arrayOfFileURLsNew); + } + } else { + if (file.includes("cdn.samagra.io")) { + if (e.target.value === '') { + olderFiles = JSON.parse(localStorage.getItem(keyToStorage)) || {}; + olderFiles[e.target.name] = { url: '', name: '', ref: '' } + localStorage.setItem(keyToStorage, JSON.stringify(olderFiles)); + olderFiles = JSON.parse(localStorage.getItem(keyToStorage)) || {}; + await formController.broadcastFormDataUpdate(form.getDataStr(), olderFiles); + } else { + console.info("File has already been uploaded"); + } + } else { + arrayOfFileURLs[e.target.name] = { url: '', name: '', ref: '' } + // Broadcast File Remove + const arrayOfFileURLsNew = { ...arrayOfFileURLs, ...olderFiles }; + if (olderFiles && olderFiles !== undefined) { + for (const [key, value] of Object.entries(olderFiles)) { + if (arrayOfFileURLs[key] === undefined) { + arrayOfFileURLsNew[key] = { url: '', name: '', ref: '' } + } + } + } + localStorage.setItem(keyToStorage, JSON.stringify(arrayOfFileURLsNew)); + olderFiles = JSON.parse(localStorage.getItem(keyToStorage)) || {}; + await formController.broadcastFormDataUpdate(form.getDataStr(), arrayOfFileURLsNew); + } + } } } - } else { - arrayOfFileURLs = arrayOfFileURLs.filter(file => formFiles.find(el => el.name == file.name)) } - // console.log({ arrayOfFileURLs }) - // this.data = (await fetch('http://localhost:3006/parse', { - // method: "POST", - // body: JSON.stringify({ xml: form.getDataStr() }), - // headers: { - // "Content-type": "application/json; charset=UTF-8" + // Broadcast File Remove + // const arrayOfFileURLsNew = { ...olderFiles, ...arrayOfFileURLs }; + // if (olderFiles && olderFiles !== undefined) { + // for (const [key, value] of Object.entries(olderFiles)) { + // if (arrayOfFileURLs[key] === undefined) { + // arrayOfFileURLsNew[key] = { url: '', name: '', ref: '' } + // } // } - // }).then(res => res.json())).data; - // if (fileURL) { - // const kk = formController.findKey(this.data, file.name, '$t', ''); - // this.data = formController.set(this.data, kk.substring(1), fileURL); // } + // localStorage.setItem(`${form.model}_${new Date().toISOString().split("T")[0]}`, JSON.stringify(arrayOfFileURLsNew)); + // await formController.broadcastFormDataUpdate(form.getDataStr(), arrayOfFileURLsNew); } - formController.broadcastFormDataUpdate(form.getDataStr(), arrayOfFileURLs); }); if (settings.offline) { document.addEventListener(events.XFormsValueChanged().type, () => { diff --git a/packages/enketo-express/public/js/src/module/file-manager.js b/packages/enketo-express/public/js/src/module/file-manager.js index da55d8610d8099a0180a61f383389d93b9d3385d..12f3be97ad18f7c36a778632eea383bb13761ebe 100644 --- a/packages/enketo-express/public/js/src/module/file-manager.js +++ b/packages/enketo-express/public/js/src/module/file-manager.js @@ -18,7 +18,7 @@ let instanceAttachments; * @return { object } promise boolean or rejection with Error */ function init() { - return Promise.resolve( true ); + return Promise.resolve(true); } /** @@ -36,7 +36,7 @@ function isWaitingForPermissions() { * * @param {{filename: string}} attachments - attachments sent with record to be loaded */ -function setInstanceAttachments( attachments ) { +function setInstanceAttachments(attachments) { instanceAttachments = attachments; } /** @@ -46,57 +46,57 @@ function setInstanceAttachments( attachments ) { * @param {?string|object} subject - File or filename * @return { object } promise url string or rejection with Error */ -function getFileUrl( subject ) { - return new Promise( ( resolve, reject ) => { - if ( !subject ) { - resolve( null ); - } else if ( typeof subject === 'string' ) { - if ( subject.startsWith( '/' ) ) { - resolve( subject ); - } else if ( instanceAttachments && ( Object.prototype.hasOwnProperty.call( instanceAttachments, subject ) ) ) { - resolve( instanceAttachments[ subject ] ); - } else if ( !store.available ) { +function getFileUrl(subject) { + return new Promise((resolve, reject) => { + if (!subject) { + resolve(null); + } else if (typeof subject === 'string') { + if (subject.startsWith('/')) { + resolve(subject); + } else if (instanceAttachments && (Object.prototype.hasOwnProperty.call(instanceAttachments, subject))) { + resolve(instanceAttachments[subject]); + } else if (!store.available) { // e.g. in an online-only edit view - reject( new Error( 'store not available' ) ); - } else if ( URL_RE.test( subject ) ) { + reject(new Error('store not available')); + } else if (URL_RE.test(subject)) { // Any URL values are default binary values. These should only occur in offline-capable views, // because the form cache module removed the src attributes // (which are /urls/like/this/http:// and are caught above this statement) - store.survey.resource.get( settings.enketoId, subject ) - .then( file => { - if ( file.item ) { - resolve( URL.createObjectURL( file.item ) ); + store.survey.resource.get(settings.enketoId, subject) + .then(file => { + if (file.item) { + resolve(URL.createObjectURL(file.item)); } else { - reject( new Error( 'File Retrieval Error' ) ); + reject(new Error('File Retrieval Error')); } - } ) - .catch( reject ); + }) + .catch(reject); } else { // obtain file from storage - store.record.file.get( _getInstanceId(), subject ) - .then( file => { - if ( file.item ) { - if ( isTooLarge( file.item ) ) { - reject( _getMaxSizeError() ); + store.record.file.get(_getInstanceId(), subject) + .then(file => { + if (file.item) { + if (isTooLarge(file.item)) { + reject(_getMaxSizeError()); } else { - resolve( URL.createObjectURL( file.item ) ); + resolve(URL.createObjectURL(file.item)); } } else { - reject( new Error( 'File Retrieval Error' ) ); + reject(new Error('File Retrieval Error')); } - } ) - .catch( reject ); + }) + .catch(reject); } - } else if ( typeof subject === 'object' ) { - if ( isTooLarge( subject ) ) { - reject( _getMaxSizeError() ); + } else if (typeof subject === 'object') { + if (isTooLarge(subject)) { + reject(_getMaxSizeError()); } else { - resolve( URL.createObjectURL( subject ) ); + resolve(URL.createObjectURL(subject)); } } else { - reject( new Error( 'Unknown error occurred' ) ); + reject(new Error('Unknown error occurred')); } - } ); + }); } /** @@ -107,15 +107,15 @@ function getFileUrl( subject ) { * @param {?string|object} subject - File or filename in local storage * @return { object } promise url string or rejection with Error */ -function getObjectUrl( subject ) { - return getFileUrl( subject ) - .then( url => { - if ( /https?:\/\//.test( url ) ) { - return connection.getMediaFile( url ).then( obj => URL.createObjectURL( obj.item ) ); +function getObjectUrl(subject) { + return getFileUrl(subject) + .then(url => { + if (/https?:\/\//.test(url)) { + return connection.getMediaFile(url).then(obj => URL.createObjectURL(obj.item)); } return url; - } ); + }); } /** @@ -124,57 +124,62 @@ function getObjectUrl( subject ) { * @return { Promise } A promise that resolves with an array of files */ function getCurrentFiles() { - const fileInputs = [ ...document.querySelectorAll( 'form.or input[type="file"], form.or input[type="text"][data-drawing="true"]' ) ]; + const fileInputs = [...document.querySelectorAll('form.or input[type="file"], form.or input[type="text"][data-drawing="true"]')]; const fileTasks = []; - const _processNameAndSize = function( input, file ){ - if ( file && file.name ) { + const _processNameAndSize = function (input, file) { + if (file && file.name) { // Correct file names by adding a unique-ish postfix // First create a clone, because the name property is immutable // TODO: in the future, when browser support increase we can invoke // the File constructor to do this. - const newFilename = getFilename( file, input.dataset.filenamePostfix ); + const newFilename = getFilename(file, input.dataset.filenamePostfix); // If file is resized, get Blob representation of data URI - if ( input.dataset.resized && input.dataset.resizedDataURI ) { - file = utils.dataUriToBlobSync( input.dataset.resizedDataURI ); + if (input.dataset.resized && input.dataset.resizedDataURI) { + file = utils.dataUriToBlobSync(input.dataset.resizedDataURI); } - file = new Blob( [ file ], { + file = new Blob([file], { type: file.type - } ); - file.name = newFilename; + }); + console.log("test"); + const parts = newFilename.split("."); + const extension = parts[parts.length - 1]; + const fileName = parts[0]; + const postfix = input.name.split("/")[input.name.split("/").length - 1]; + file.name = `${fileName}.${postfix}.${extension}`; } return file; }; - fileInputs.forEach( input => { - if ( input.type === 'file' ) { + fileInputs.forEach(input => { + if (input.type === 'file') { // first get any files inside file input elements - if ( input.files[0] ){ - fileTasks.push( Promise.resolve( _processNameAndSize( input, input.files[ 0 ] ) ) ); + if (input.files[0]) { + fileTasks.push(Promise.resolve(_processNameAndSize(input, input.files[0]))); } - } else if ( input.value ) { + } else if (input.value) { // then from canvases - const canvas = input.closest( '.question' ).querySelector( '.draw-widget canvas' ); - if ( canvas && !URL_RE.test( input.value ) ) { - fileTasks.push( new Promise( resolve => canvas.toBlob( blob => { + const canvas = input.closest('.question').querySelector('.draw-widget canvas'); + if (canvas && !URL_RE.test(input.value)) { + fileTasks.push(new Promise(resolve => canvas.toBlob(blob => { blob.name = input.value; - resolve( _processNameAndSize( input, blob ) ); - } ) ) ); + resolve(_processNameAndSize(input, blob)); + }))); } } - } ); + }); - return Promise.all( fileTasks ) - .then( files => { + return Promise.all(fileTasks) + .then(files => { // get any file names of files that were loaded as DataURI and have remained unchanged (i.e. loaded from Storage) fileInputs - .filter( input => input.matches( '[data-loaded-file-name]' ) ) - .forEach( input => files.push( input.getAttribute( 'data-loaded-file-name' ) ) ); + .filter(input => input.matches('[data-loaded-file-name]')) + .forEach(input => files.push(input.getAttribute('data-loaded-file-name'))); return files; - } ); + }); } /** @@ -183,10 +188,10 @@ function getCurrentFiles() { * @param { string } filename - filename * @return { Promise } array of files */ -function getCurrentFile( filename ) { +function getCurrentFile(filename) { // relies on all file names to be unique (which they are) return getCurrentFiles() - .then( files => files.find( file => file.name === filename ) ); + .then(files => files.find(file => file.name === filename)); } /** @@ -204,14 +209,14 @@ function _getInstanceId() { * @param { object } file - the File * @return { boolean } whether file is too large */ -function isTooLarge( file ) { +function isTooLarge(file) { return file && file.size > _getMaxSize(); } function _getMaxSizeError() { - return new Error( t( 'filepicker.toolargeerror', { + return new Error(t('filepicker.toolargeerror', { maxSize: getMaxSizeReadable() - } ) ); + })); } /** @@ -224,7 +229,7 @@ function _getMaxSize() { } function getMaxSizeReadable() { - return `${Math.round( _getMaxSize() * 100 / ( 1000 * 1000 * 100 ) )}MB`; + return `${Math.round(_getMaxSize() * 100 / (1000 * 1000 * 100))}MB`; } export default { diff --git a/packages/enketo-express/public/js/src/module/form-controller.js b/packages/enketo-express/public/js/src/module/form-controller.js index abebc75bc0e13ac942930a2c5a3691b115111474..5becf924fddc3a24d98018ac92113773ce582aad 100644 --- a/packages/enketo-express/public/js/src/module/form-controller.js +++ b/packages/enketo-express/public/js/src/module/form-controller.js @@ -218,6 +218,18 @@ export class FormController { } async broadcastFormDataUpdate(xml, fileURLs) { + console.log("Broadcasting file update") + // broadcast form data to parent window + window.parent.postMessage(JSON.stringify({ + formData: xml, + formXML: xml, + state: this._state, + fileURLs: fileURLs + }), '*'); + } + + async broadcastFileRemoveUpdate(xml, fileURLs) { + console.log("Broadcasting file update") // broadcast form data to parent window window.parent.postMessage(JSON.stringify({ formData: xml, diff --git a/packages/form-manager/package.json b/packages/form-manager/package.json index 93a62477c8f287d48021144196595c375fbe781e..14e714e007790a2e7d6fdce2314c08ddf08dd1a0 100644 --- a/packages/form-manager/package.json +++ b/packages/form-manager/package.json @@ -26,6 +26,9 @@ "@nestjs/core": "^8.0.0", "@nestjs/platform-express": "^8.0.0", "@nestjs/platform-fastify": "^9.2.1", + "cache-manager": "^4.1.0", + "cache-manager-redis-store": "^3.0.1", + "ioredis": "^5.3.1", "minio": "^7.0.32", "reflect-metadata": "^0.1.13", "request": "^2.88.2", @@ -35,7 +38,8 @@ "webpack": "^5.74.0", "xml2js": "^0.4.23", "xml2json": "^0.12.0", - "xmldom": "^0.6.0" + "xmldom": "^0.6.0", + "localforage": "^1.10.0" }, "devDependencies": { "@nestjs/cli": "^8.0.0", diff --git a/packages/form-manager/src/app.controller.ts b/packages/form-manager/src/app.controller.ts index 3c3975f8422ef7358e65d385ca8e14694a87994c..e378b52b97d319b7264c9f17dfb2d113158f9b28 100644 --- a/packages/form-manager/src/app.controller.ts +++ b/packages/form-manager/src/app.controller.ts @@ -1,9 +1,11 @@ import { Body, + CACHE_MANAGER, Controller, Get, HttpException, HttpStatus, + Inject, Param, Post, Query, @@ -14,6 +16,8 @@ import { FileInterceptor } from '@nestjs/platform-express/multer'; import { AppService } from './app.service'; import { v4 as uuidv4 } from 'uuid'; +import { Cache } from 'cache-manager'; + // eslint-disable-next-line @typescript-eslint/no-var-requires const Minio = require('minio'); @@ -38,9 +42,10 @@ type PrefillDto = { @Controller() export class AppController { constructor( + @Inject(CACHE_MANAGER) private cacheManager: Cache, private readonly appService: AppService, private configService: ConfigService, - ) { } + ) {} getLoginToken = () => { try { @@ -62,7 +67,7 @@ export class AppController { json: postData, }; - console.log(options) + console.log(options); return new Promise((resolve, reject) => { request(options, function (error, response, body) { @@ -167,23 +172,50 @@ export class AppController { } @Post('prefillXML') - prefillXML( + async prefillXML( @Query('form') form, @Query('onFormSuccessData') onFormSuccessData, @Body('prefillXML') prefillXML, - ): string { + @Body('imageUrls') files, + ): Promise<string> { try { if (onFormSuccessData) { - return this.appService.prefillFormXML( + const prefilledForm = this.appService.prefillFormXML( form, onFormSuccessData, prefillXML, + files, ); + const instanceId = uuidv4(); + await this.cacheManager.set(instanceId, prefilledForm, 86400 * 10); + return `${this.configService.get( + 'SERVER_BASR_URL', + )}form/instance/${instanceId}`; } else { - return "OK"; + return 'OK'; } } catch (e) { - return "OK2"; + console.error(e); + return 'OK2'; + } + } + + @Post('submissionXML') + async submissionXML( + @Query('form') form, + @Body('prefillXML') prefillXML, + @Body('imageUrls') files, + ): Promise<string> { + try { + const submissionFormXML = this.appService.submissionFormXML( + form, + prefillXML, + files, + ); + return submissionFormXML; + } catch (e) { + console.error(e); + return 'OK2'; } } @@ -192,6 +224,14 @@ export class AppController { return this.appService.getForm(id); } + @Get('form/instance/:instanceId') + async getFormWithInstanceID( + @Param('instanceId') instanceId, + ): Promise<string> { + const xml = await this.cacheManager.get(instanceId); + return xml; + } + @Post('parse') parseXML(@Body() xml: any): any { // console.log({ xml }) @@ -200,19 +240,27 @@ export class AppController { } @Get('osceForm/:type/:year/:speciality?') - getOsceForm(@Param('type') type, @Param('year') year, @Param('speciality') speciality): any { + getOsceForm( + @Param('type') type, + @Param('year') year, + @Param('speciality') speciality, + ): any { return this.appService.getOsceForms(type, year, speciality); } @Get('osceFormTeachers/:type/:year/:speciality?') - getOsceFormTeachers(@Param('type') type, @Param('year') year, @Param('speciality') speciality): any { + getOsceFormTeachers( + @Param('type') type, + @Param('year') year, + @Param('speciality') speciality, + ): any { return this.appService.getOsceForms(type, year, speciality, 2); } @Post('form/uploadFile') @UseInterceptors(FileInterceptor('file')) async uploadFile(@UploadedFile() file: Express.Multer.File) { - console.log(file) + console.log(file); const extension = file.originalname.split('.').pop(); const fileName = uuidv4() + `.${extension}`; const tokenRes = await this.getLoginToken(); @@ -252,7 +300,7 @@ export class AppController { 'MINIO_BUCKET_ID', )}/${fileName}`; - console.log("Uploaded File:", fileURL); + console.log('Uploaded File:', fileURL); return { fileURL }; } diff --git a/packages/form-manager/src/app.module.ts b/packages/form-manager/src/app.module.ts index 7042665f9f87a252b365be7344890012d961971d..c0c58347ec7cc7ecc2288a6b8157ed8a471c9fcb 100644 --- a/packages/form-manager/src/app.module.ts +++ b/packages/form-manager/src/app.module.ts @@ -1,16 +1,34 @@ -import { Module } from '@nestjs/common'; +import { CacheModule, CacheStore, Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; -import { ConfigModule } from '@nestjs/config'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { redisStore } from 'cache-manager-redis-store'; +import type { RedisClientOptions } from 'redis'; @Module({ imports: [ + CacheModule.registerAsync({ + isGlobal: true, + useFactory: async (configService: ConfigService) => { + const store = await redisStore({ + socket: { + host: '0.0.0.0', + port: 6369, + }, + }); + return { + store: store as unknown as CacheStore, + ttl: 5, + }; + }, + }), ConfigModule.forRoot({ isGlobal: true, envFilePath: '.development.env', }), + // CacheModule.register(), ], controllers: [AppController], providers: [AppService], }) -export class AppModule { } +export class AppModule {} diff --git a/packages/form-manager/src/app.service.ts b/packages/form-manager/src/app.service.ts index fdf05038f1843e135566b34d97084013e12bb87f..d8cde6f9bb6581db47664903b38fb4c2bcdc2527 100644 --- a/packages/form-manager/src/app.service.ts +++ b/packages/form-manager/src/app.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DOMParser } from 'xmldom'; import * as fs from 'fs'; import { join } from 'path'; @@ -6,6 +6,7 @@ import { join } from 'path'; @Injectable() export class AppService { parser = new DOMParser(); + pf = ''; getHello(): string { return 'Hello World!'; @@ -16,25 +17,34 @@ export class AppService { return fs.readFileSync(formFilePath, 'utf8'); } - getOsceForms(type?: string, year?: string, speciality?: string, noOfForms?: number) { + getOsceForms( + type?: string, + year?: string, + speciality?: string, + noOfForms?: number, + ) { try { - if (!type || !year) - return "Please provide valid inputs" + if (!type || !year) return 'Please provide valid inputs'; - const matchingText = speciality ? `${type}_${year}_${speciality}` : `${type}_${year}`; + const matchingText = speciality + ? `${type}_${year}_${speciality}` + : `${type}_${year}`; let matchingFiles = []; // const fileNames = fs.readdirSync(`/Users/amitsharma/Projects/workflow/packages/form-manager/src/forms`); - const fileNames = fs.readdirSync(__dirname + "/forms"); + const fileNames = fs.readdirSync(__dirname + '/forms'); - fileNames.forEach(file => { if (file.startsWith(matchingText)) matchingFiles.push(file) }) + fileNames.forEach((file) => { + if (file.startsWith(matchingText)) matchingFiles.push(file); + }); if (matchingFiles.length) { if (noOfForms) { const names = []; for (let i = 0; i < noOfForms; i++) { - let form = matchingFiles[Math.floor(Math.random() * matchingFiles.length)]; + let form = + matchingFiles[Math.floor(Math.random() * matchingFiles.length)]; names.push(form); - matchingFiles = matchingFiles.filter(el => el != form); + matchingFiles = matchingFiles.filter((el) => el != form); } return names; } @@ -79,7 +89,11 @@ export class AppService { const key_arr = key.split('_*_'); let element = null; if (this.isImage(prefillSpec[key])) { - const parentEl = this.findElementRecursively(0, key_arr.slice(0, key_arr.length - 1), instance); + const parentEl = this.findElementRecursively( + 0, + key_arr.slice(0, key_arr.length - 1), + instance, + ); for (let i = 0; i < parentEl.childNodes.length; i++) { if (element) break; if (parentEl.childNodes[i].tagName == key_arr[key_arr.length - 1]) { @@ -91,7 +105,6 @@ export class AppService { } } } - } if (element) { element.textContent = eval(prefillSpec[key]); @@ -102,26 +115,201 @@ export class AppService { } isImage(filename: string): boolean { - if (filename.includes(".png") || filename.includes(".tif") || filename.includes(".tiff") || filename.includes(".jpg") || filename.includes(".jpeg") || filename.includes(".bmp") || filename.includes(".gif") || filename.includes(".eps")) + if ( + filename.includes('.png') || + filename.includes('.tif') || + filename.includes('.tiff') || + filename.includes('.jpg') || + filename.includes('.jpeg') || + filename.includes('.bmp') || + filename.includes('.gif') || + filename.includes('.eps') + ) return true; return false; } findElementRecursively(start: number, key_arr: any, instance: any) { if (!instance) return null; - if (!key_arr[start + 1]) return instance.getElementsByTagName(key_arr[start])?.[0] - return this.findElementRecursively(start + 1, key_arr, instance.getElementsByTagName(key_arr[start])?.[0]) + if (!key_arr[start + 1]) + return instance.getElementsByTagName(key_arr[start])?.[0]; + return this.findElementRecursively( + start + 1, + key_arr, + instance.getElementsByTagName(key_arr[start])?.[0], + ); } - prefillFormXML(form: string, onFormSuccessData: any, prefillSpec: any): string { + prefillFormXML( + form: string, + onFormSuccessData: any, + prefillSpec: any, + files: any, + ): string { const formFilePath = join(__dirname, `forms/${form}.xml`); const formString = fs.readFileSync(formFilePath, 'utf8'); const doc = this.parser.parseFromString(formString, 'text/xml'); - console.log({ prefillSpec }) - const instance = doc.getElementsByTagName('instance')[0]; - if (instance) { - instance.textContent = prefillSpec; + const instanceFromForm = doc.getElementsByTagName('instance')[0]; + console.log({ form, prefillSpec, files }); + + if (prefillSpec !== undefined) { + let instanceData = this.parser.parseFromString(prefillSpec, 'text/xml'); + if (files) { + for (const [key, value] of Object.entries(files)) { + instanceData = this.setElementByPath( + instanceData, + key, + value, + ).cloneNode(true); + console.log('instance after 1 cycle', instanceData.toString()); + // this.walk(instanceData, prefillSpec, key, value); + // instanceData = this.parser.parseFromString(this.pf, 'text/xml'); + } + } + console.log(instanceData.toString()); + doc + .getElementsByTagName('instance')[0] + .replaceChild(instanceData, instanceFromForm); } + + console.log(doc.toString().length); return doc.toString(); } + + submissionFormXML(form: string, prefillSpec: any, files: any): string { + const formFilePath = join(__dirname, `forms/${form}.xml`); + const formString = fs.readFileSync(formFilePath, 'utf8'); + const doc = this.parser.parseFromString(formString, 'text/xml'); + const instanceFromForm = doc.getElementsByTagName('instance')[0]; + console.log({ form, prefillSpec, files }); + + if (prefillSpec !== undefined) { + let instanceData = this.parser.parseFromString(prefillSpec, 'text/xml'); + if (files) { + for (const [key, value] of Object.entries(files)) { + instanceData = this.setElementByPath( + instanceData, + key, + value, + ).cloneNode(true); + console.log('instance after 1 cycle', instanceData.toString()); + // this.walk(instanceData, prefillSpec, key, value); + // instanceData = this.parser.parseFromString(this.pf, 'text/xml'); + } + } + console.log(instanceData.toString()); + doc + .getElementsByTagName('instance')[0] + .replaceChild(instanceData, instanceFromForm); + return instanceData.toString(); + } else { + return instanceFromForm.toString(); + } + } + + setElementByPath(doc, path, value) { + const pathParts = path.split('/'); + let node = doc; + let tree = []; + tree.push(node.cloneNode(true)); + for (let i = 1; i < pathParts.length - 1; i++) { + console.log(pathParts[i], '||', node.toString()); + node = node.getElementsByTagName(pathParts[i + 1])[0]; + tree.push(node); + } + + console.log('root', tree[pathParts.length - 2].toString()); + + let originalURLNode = tree[pathParts.length - 2].cloneNode(true); + originalURLNode.textContent = value['url']; + + let rootNode = tree[pathParts.length - 2].nextSibling.cloneNode(true); + rootNode.textContent = value['url']; + tree[pathParts.length - 2] = tree[pathParts.length - 2].nextSibling; + + console.log( + 'RootNode ****************', + '\n', + rootNode.toString(), + '\n', + tree[pathParts.length - 2].toString(), + '\n', + tree[pathParts.length - 3].toString(), + '\n', + ); + + // parts = 7 + + // T[0] = data + // T[1] = l1 + // T[2] = l2 + // T[3] = l3 + // T[4] = l4 + // T[5] = <url78/> + + // rootNode = <url78>url</url78> + + // rootNode = T[4].replaceChild(rootNode, T[5]).cloneNode(true) + // rootNode = T[3].replaceChild(rootNode, T[4]).cloneNode(true) + // rootNode = T[2].replaceChild(rootNode, T[3]).cloneNode(true) + // rootNode = T[1].replaceChild(rootNode, T[2]).cloneNode(true) + // rootNode = T[0].replaceChild(rootNode, T[1]).cloneNode(true) + for (let j = pathParts.length - 3; j >= 0; j--) { + console.log( + j, + 'pathParts', + pathParts[j + 2], + '\n', + 'Parent', + tree[j].toString(), + '\n', + 'Old Child', + tree[j + 1].toString(), + '\n', + 'New Child', + rootNode.toString(), + ); + console.log(''); + // Edge case + let oldChild = tree[j].getElementsByTagName(pathParts[j + 2])[0]; + if (j + 2 === pathParts.length - 1) { + tree[j].replaceChild(originalURLNode, oldChild); + oldChild = oldChild.nextSibling; + } + tree[j].replaceChild(rootNode, oldChild); + rootNode = tree[j].cloneNode(true); + } + console.log('After replace T[0]', rootNode.toString()); + console.log('After replace T[1]', tree[1].toString()); + // tree[0].replaceChild(rootNode, tree[1]); + return rootNode; + } + + walk(node, prefillSpec, key, value) { + try { + var children = node.childNodes; + if (children) { + for ( + var i = 0; + i < children.length; + i++ // Children are siblings to each other + ) + this.walk(children[i], prefillSpec, key, value); + const nodeName = key.split('/')[key.split('/').length - 1]; + if (node.nodeName === nodeName || node.tagName === nodeName) { + const xmlString = `<${node.nextSibling.tagName}>${value['url']}</${node.nextSibling.tagName}>`; + console.log(`<${node.nextSibling.tagName}/>`, xmlString); + prefillSpec = prefillSpec.replace( + `<${node.nextSibling.tagName}/>`, + xmlString, + ); + this.pf = prefillSpec; + return prefillSpec; + } + } + } catch (e) { + console.error(e); + console.log('Update Done'); + } + } } diff --git a/packages/form-manager/src/workflow.code-workspace b/packages/form-manager/src/workflow.code-workspace new file mode 100644 index 0000000000000000000000000000000000000000..d9480f3881c2df37f9b49b311ff3624eeb529525 --- /dev/null +++ b/packages/form-manager/src/workflow.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": "../../.." + }, + { + "path": "../../../../UP-HRH" + } + ], + "settings": {} +} \ No newline at end of file