mirror of
https://github.com/sasjs/server.git
synced 2026-01-07 06:30:06 +00:00
chore: merging with sessions - sas request with file upload
This commit is contained in:
@@ -5,6 +5,7 @@ import { configuration } from '../../package.json'
|
||||
import { promisify } from 'util'
|
||||
import { execFile } from 'child_process'
|
||||
import { Session } from '../types'
|
||||
import { generateFileUploadSasCode } from '../utils'
|
||||
const execFilePromise = promisify(execFile)
|
||||
|
||||
export class ExecutionController {
|
||||
@@ -12,7 +13,8 @@ export class ExecutionController {
|
||||
program = '',
|
||||
autoExec?: string,
|
||||
session?: Session,
|
||||
vars?: any
|
||||
vars?: any,
|
||||
otherArgs?: any
|
||||
) {
|
||||
if (program) {
|
||||
if (!(await fileExists(program))) {
|
||||
@@ -39,10 +41,26 @@ export class ExecutionController {
|
||||
|
||||
let webout = path.join(session.path, 'webout.txt')
|
||||
await createFile(webout, '')
|
||||
|
||||
program = `
|
||||
%let sasjsprocessmode=Stored Program;
|
||||
filename _webout "${webout}";
|
||||
${program}`
|
||||
|
||||
program = `filename _webout "${webout}";\n${program}`
|
||||
// if no files are uploaded filesNamesMap will be undefined
|
||||
if (otherArgs && otherArgs.filesNamesMap) {
|
||||
const uploadSasCode = generateFileUploadSasCode(
|
||||
otherArgs.filesNamesMap,
|
||||
session.path
|
||||
)
|
||||
|
||||
const code = path.join(session.path, 'code.sas')
|
||||
//If sas code for the file is generated it will be appended to the top of sasCode
|
||||
if (uploadSasCode.length > 0) {
|
||||
program = `${uploadSasCode}` + program
|
||||
}
|
||||
}
|
||||
|
||||
let code = path.join(session.path, 'code.sas')
|
||||
if (!(await fileExists(code))) {
|
||||
await createFile(code, program)
|
||||
}
|
||||
@@ -64,7 +82,9 @@ export class ExecutionController {
|
||||
if (await fileExists(log)) log = await readFile(log)
|
||||
else log = ''
|
||||
|
||||
if (stderr) return Promise.reject({ error: stderr, log: log })
|
||||
// if (stderr) {
|
||||
// return Promise.reject({ error: stderr, log: log })
|
||||
// }
|
||||
|
||||
if (await fileExists(webout)) webout = await readFile(webout)
|
||||
else webout = ''
|
||||
@@ -73,7 +93,7 @@ export class ExecutionController {
|
||||
(key: string) => key.toLowerCase() === '_debug'
|
||||
)
|
||||
|
||||
if (debug && vars[debug] >= 131) {
|
||||
if (debug && vars[debug] >= 131 || stderr) {
|
||||
webout = `<html><body>
|
||||
${webout}
|
||||
<div style="text-align:left">
|
||||
@@ -82,7 +102,7 @@ ${webout}
|
||||
</div>
|
||||
</body></html>`
|
||||
}
|
||||
|
||||
|
||||
session.inUse = false
|
||||
|
||||
sessionController.deleteSession(session)
|
||||
|
||||
@@ -70,7 +70,7 @@ export class SessionController {
|
||||
|
||||
this.scheduleSessionDestroy(session)
|
||||
|
||||
this.executionController.execute('', autoExec, session).catch(() => {})
|
||||
this.executionController.execute('', autoExec, session).catch((err) => {})
|
||||
|
||||
this.sessions.push(session)
|
||||
|
||||
|
||||
@@ -1,12 +1,41 @@
|
||||
import express from 'express'
|
||||
import { createFileTree, getTreeExample } from '../controllers'
|
||||
import { createFileTree, getSessionController, getTreeExample } from '../controllers'
|
||||
import { ExecutionResult, isRequestQuery, isFileTree } from '../types'
|
||||
import path from 'path'
|
||||
import { getTmpFilesFolderPath } from '../utils'
|
||||
import { getTmpFilesFolderPath, getTmpFolderPath, makeFilesNamesMap } from '../utils'
|
||||
import { ExecutionController } from '../controllers'
|
||||
import { uuidv4 } from '@sasjs/utils'
|
||||
|
||||
const multer = require('multer')
|
||||
const router = express.Router()
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: function (req: any, file: any, cb: any) {
|
||||
//Sending the intercepted files to the sessions subfolder
|
||||
cb(null, req.sasSession.path)
|
||||
},
|
||||
filename: function (req: any, file: any, cb: any) {
|
||||
//req_file prefix + unique hash added to sas request files
|
||||
cb(null, `req_file_${uuidv4().replace(/-/gm, '')}`)
|
||||
}
|
||||
})
|
||||
|
||||
const upload = multer({ storage: storage })
|
||||
|
||||
//It will intercept request and generate uniqe uuid to be used as a subfolder name
|
||||
//that will store the files uploaded
|
||||
const preuploadMiddleware = async (req: any, res: any, next: any) => {
|
||||
let session
|
||||
|
||||
const sessionController = getSessionController()
|
||||
session = await sessionController.getSession()
|
||||
session.inUse = true
|
||||
|
||||
req.sasSession = session
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
router.get('/', async (_, res) => {
|
||||
res.status(200).send('Welcome to @sasjs/server API')
|
||||
})
|
||||
@@ -46,9 +75,12 @@ router.get('/SASjsExecutor', async (req, res) => {
|
||||
|
||||
router.get('/SASjsExecutor/do', async (req, res) => {
|
||||
if (isRequestQuery(req.query)) {
|
||||
const sasCodePath = path
|
||||
let sasCodePath = path
|
||||
.join(getTmpFilesFolderPath(), req.query._program)
|
||||
.replace(new RegExp('/', 'g'), path.sep)
|
||||
|
||||
// If no extension provided, add .sas extension
|
||||
sasCodePath += !sasCodePath.includes('.') ? '.sas' : ''
|
||||
|
||||
await new ExecutionController()
|
||||
.execute(sasCodePath, undefined, undefined, { ...req.query })
|
||||
@@ -70,4 +102,39 @@ router.get('/SASjsExecutor/do', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/SASjsExecutor/do', preuploadMiddleware, upload.any(), async (req: any, res: any) => {
|
||||
if (isRequestQuery(req.query)) {
|
||||
let sasCodePath = path
|
||||
.join(getTmpFilesFolderPath(), req.query._program)
|
||||
.replace(new RegExp('/', 'g'), path.sep)
|
||||
|
||||
// If no extension provided, add .sas extension
|
||||
sasCodePath += !sasCodePath.includes('.') ? '.sas' : ''
|
||||
|
||||
let filesNamesMap = null
|
||||
|
||||
if (req.files && req.files.length > 0) {
|
||||
filesNamesMap = makeFilesNamesMap(req.files)
|
||||
}
|
||||
|
||||
await new ExecutionController()
|
||||
.execute(sasCodePath, undefined, req.sasSession, { ...req.query }, { filesNamesMap: filesNamesMap })
|
||||
.then((result: {}) => {
|
||||
res.status(200).send(result)
|
||||
})
|
||||
.catch((err: {} | string) => {
|
||||
res.status(400).send({
|
||||
status: 'failure',
|
||||
message: 'Job execution failed.',
|
||||
...(typeof err === 'object' ? err : { details: err })
|
||||
})
|
||||
})
|
||||
} else {
|
||||
res.status(400).send({
|
||||
status: 'failure',
|
||||
message: `Please provide the location of SAS code`
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './file'
|
||||
export * from './sleep'
|
||||
export * from './upload'
|
||||
86
src/utils/upload.ts
Normal file
86
src/utils/upload.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { getTmpSessionsFolderPath } from '.'
|
||||
|
||||
/**
|
||||
* It will create a object that maps hashed file names to the original names
|
||||
* @param files array of files to be mapped
|
||||
* @returns object
|
||||
*/
|
||||
export const makeFilesNamesMap = (files: any) => {
|
||||
if (!files) return null
|
||||
|
||||
const filesNamesMap: any = {}
|
||||
|
||||
for (let file of files) {
|
||||
filesNamesMap[file.filename] = file.fieldname
|
||||
}
|
||||
|
||||
return filesNamesMap
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the sas code that reference uploaded files in the concurrent request
|
||||
* @param filesNamesMap object that maps hashed file names and original file names
|
||||
* @param sasUploadFolder name of the folder that is created for the purpose of files in concurrent request
|
||||
* @returns generated sas code
|
||||
*/
|
||||
export const generateFileUploadSasCode = (
|
||||
filesNamesMap: any,
|
||||
sasSessionFolder: string
|
||||
): string => {
|
||||
const uploadFilesDirPath = sasSessionFolder
|
||||
|
||||
let uploadSasCode = ''
|
||||
let fileCount = 0
|
||||
let uploadedFilesMap: {
|
||||
fileref: string
|
||||
filepath: string
|
||||
filename: string
|
||||
count: number
|
||||
}[] = []
|
||||
|
||||
fs.readdirSync(uploadFilesDirPath).forEach((fileName) => {
|
||||
let fileCountString = fileCount < 100 ? '0' + fileCount : fileCount
|
||||
fileCountString = fileCount < 10 ? '00' + fileCount : fileCount
|
||||
|
||||
if (fileName.includes('req_file')) {
|
||||
fileCount++
|
||||
|
||||
uploadedFilesMap.push({
|
||||
fileref: `_sjs${fileCountString}`,
|
||||
filepath: `${uploadFilesDirPath}/${fileName}`,
|
||||
filename: filesNamesMap[fileName],
|
||||
count: fileCount
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
for (let uploadedMap of uploadedFilesMap) {
|
||||
uploadSasCode += `\nfilename ${uploadedMap.fileref} "${uploadedMap.filepath}";`
|
||||
}
|
||||
|
||||
uploadSasCode += `\n%let _WEBIN_FILE_COUNT=${fileCount};`
|
||||
|
||||
for (let uploadedMap of uploadedFilesMap) {
|
||||
uploadSasCode += `\n%let _WEBIN_FILENAME${uploadedMap.count}=${uploadedMap.filepath};`
|
||||
}
|
||||
|
||||
for (let uploadedMap of uploadedFilesMap) {
|
||||
uploadSasCode += `\n%let _WEBIN_FILEREF${uploadedMap.count}=${uploadedMap.fileref};`
|
||||
}
|
||||
|
||||
for (let uploadedMap of uploadedFilesMap) {
|
||||
uploadSasCode += `\n%let _WEBIN_NAME${uploadedMap.count}=${uploadedMap.filename};`
|
||||
}
|
||||
|
||||
if (fileCount > 0) {
|
||||
uploadSasCode += `\n%let _WEBIN_NAME=&_WEBIN_NAME1;`
|
||||
uploadSasCode += `\n%let _WEBIN_FILEREF=&_WEBIN_FILEREF1;`
|
||||
uploadSasCode += `\n%let _WEBIN_FILENAME=&_WEBIN_FILENAME1;`
|
||||
}
|
||||
|
||||
uploadSasCode += `\n`
|
||||
|
||||
return uploadSasCode
|
||||
}
|
||||
Reference in New Issue
Block a user