1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 19:34:34 +00:00

Merge branch 'master' into homepage-sasjs-executor

Conflicts:
	api/src/app.ts
	api/src/routes/index.ts
	api/src/routes/spec/routes.spec.ts
This commit is contained in:
2021-11-01 13:12:52 +00:00
11 changed files with 336 additions and 285 deletions

View File

@@ -1,11 +1,16 @@
import express from 'express' import express from 'express'
import indexRouter from './routes' import webRouter from './routes/web'
import path from 'path' import apiRouter from './routes/api'
import { getWebBuildFolderPath } from './utils' import { getWebBuildFolderPath } from './utils'
const app = express() const app = express()
app.use(express.json({ limit: '50mb' })) app.use(express.json({ limit: '50mb' }))
app.use('/', indexRouter)
app.use('/', webRouter)
app.use('/SASjsApi', apiRouter)
app.use(express.json({ limit: '50mb' }))
app.use(express.static(getWebBuildFolderPath())) app.use(express.static(getWebBuildFolderPath()))
export default app export default app

View File

@@ -16,7 +16,8 @@ export class ExecutionController {
autoExec?: string, autoExec?: string,
session?: Session, session?: Session,
vars?: any, vars?: any,
otherArgs?: any otherArgs?: any,
returnJson?: boolean
) { ) {
if (program) { if (program) {
if (!(await fileExists(program))) { if (!(await fileExists(program))) {
@@ -91,6 +92,7 @@ ${program}`
(key: string) => key.toLowerCase() === '_debug' (key: string) => key.toLowerCase() === '_debug'
) )
let jsonResult
if ((debug && vars[debug] >= 131) || stderr) { if ((debug && vars[debug] >= 131) || stderr) {
webout = `<html><body> webout = `<html><body>
${webout} ${webout}
@@ -99,13 +101,15 @@ ${webout}
<pre>${log}</pre> <pre>${log}</pre>
</div> </div>
</body></html>` </body></html>`
} else if (returnJson) {
jsonResult = { result: webout, log: log }
} }
session.inUse = false session.inUse = false
sessionController.deleteSession(session) sessionController.deleteSession(session)
return Promise.resolve(webout) return Promise.resolve(jsonResult || webout)
} }
buildDirectorytree() { buildDirectorytree() {

View File

@@ -14,7 +14,9 @@ export const createFileTree = async (
) )
await asyncForEach(members, async (member: FolderMember | ServiceMember) => { await asyncForEach(members, async (member: FolderMember | ServiceMember) => {
const name = member.name let name = member.name
if (member.type === 'service') name += '.sas'
if (member.type === MemberType.folder) { if (member.type === MemberType.folder) {
await createFolder(path.join(destinationPath, name)).catch((err) => await createFolder(path.join(destinationPath, name)).catch((err) =>

View File

@@ -0,0 +1,85 @@
import express from 'express'
import path from 'path'
import { createFileTree, getTreeExample, DriveController, ExecutionController } from '../../controllers'
import { isFileTree, isFileQuery } from '../../types'
import { getTmpFilesFolderPath } from '../../utils'
const driveRouter = express.Router()
driveRouter.post('/deploy', async (req, res) => {
if (!isFileTree(req.body.fileTree)) {
res.status(400).send({
status: 'failure',
message: 'Provided not supported data format.',
example: getTreeExample()
})
return
}
await createFileTree(
req.body.fileTree.members,
req.body.appLoc ? req.body.appLoc.replace(/^\//, '').split('/') : []
)
.then(() => {
res.status(200).send({
status: 'success',
message: 'Files deployed successfully to @sasjs/server.'
})
})
.catch((err) => {
res
.status(500)
.send({ status: 'failure', message: 'Deployment failed!', ...err })
})
})
driveRouter.get('/files', async (req, res) => {
if (isFileQuery(req.query)) {
const filePath = path
.join(getTmpFilesFolderPath(), req.query.filePath)
.replace(new RegExp('/', 'g'), path.sep)
await new DriveController()
.readFile(filePath)
.then((fileContent) => {
res.status(200).send({ status: 'success', fileContent: fileContent })
})
.catch((err) => {
res.status(400).send({
status: 'failure',
message: 'File request failed.',
...(typeof err === 'object' ? err : { details: err })
})
})
} else {
res.status(400).send({
status: 'failure',
message: 'Invalid Request: Expected parameter filePath was not provided'
})
}
})
driveRouter.patch('/files', async (req, res) => {
const filePath = path
.join(getTmpFilesFolderPath(), req.body.filePath)
.replace(new RegExp('/', 'g'), path.sep)
await new DriveController()
.updateFile(filePath, req.body.fileContent)
.then(() => {
res.status(200).send({ status: 'success' })
})
.catch((err) => {
res.status(400).send({
status: 'failure',
message: 'File request failed.',
...(typeof err === 'object' ? err : { details: err })
})
})
})
driveRouter.get('/fileTree', async (req, res) => {
const tree = new ExecutionController().buildDirectorytree()
res.status(200).send({ status: 'success', tree })
})
export default driveRouter

View File

@@ -0,0 +1,10 @@
import express from 'express'
import driveRouter from './drive'
import stpRouter from './stp'
const router = express.Router()
router.use('/drive', driveRouter)
router.use('/stp', stpRouter)
export default router

View File

@@ -0,0 +1,114 @@
import request from 'supertest'
import app from '../../../app'
import { getTreeExample } from '../../../controllers/deploy'
import { getTmpFilesFolderPath } from '../../../utils/file'
import { folderExists, fileExists, readFile, deleteFolder } from '@sasjs/utils'
import path from 'path'
describe('files', () => {
describe('deploy', () => {
const shouldFailAssertion = async (payload: any) => {
const res = await request(app)
.post('/SASjsApi/drive/deploy')
.send(payload)
expect(res.statusCode).toEqual(400)
expect(res.body).toEqual({
status: 'failure',
message: 'Provided not supported data format.',
example: getTreeExample()
})
}
it('should respond with payload example if valid payload was not provided', async () => {
await shouldFailAssertion(null)
await shouldFailAssertion(undefined)
await shouldFailAssertion('data')
await shouldFailAssertion({})
await shouldFailAssertion({
userId: 1,
title: 'test is cool'
})
await shouldFailAssertion({
membersWRONG: []
})
await shouldFailAssertion({
members: {}
})
await shouldFailAssertion({
members: [
{
nameWRONG: 'jobs',
type: 'folder',
members: []
}
]
})
await shouldFailAssertion({
members: [
{
name: 'jobs',
type: 'WRONG',
members: []
}
]
})
await shouldFailAssertion({
members: [
{
name: 'jobs',
type: 'folder',
members: [
{
name: 'extract',
type: 'folder',
members: [
{
name: 'makedata1',
type: 'service',
codeWRONG: '%put Hello World!;'
}
]
}
]
}
]
})
})
it('should respond with payload example if valid payload was not provided', async () => {
const res = await request(app)
.post('/SASjsApi/drive/deploy')
.send({ fileTree: getTreeExample() })
expect(res.statusCode).toEqual(200)
expect(res.text).toEqual(
'{"status":"success","message":"Files deployed successfully to @sasjs/server."}'
)
await expect(folderExists(getTmpFilesFolderPath())).resolves.toEqual(true)
const testJobFolder = path.join(
getTmpFilesFolderPath(),
'jobs',
'extract'
)
await expect(folderExists(testJobFolder)).resolves.toEqual(true)
const testJobFile =
path.join(
testJobFolder,
getTreeExample().members[0].members[0].members[0].name
) + '.sas'
console.log(`[testJobFile]`, testJobFile)
await expect(fileExists(testJobFile)).resolves.toEqual(true)
await expect(readFile(testJobFile)).resolves.toEqual(
getTreeExample().members[0].members[0].members[0].code
)
await deleteFolder(getTmpFilesFolderPath())
})
})
})

59
api/src/routes/api/stp.ts Normal file
View File

@@ -0,0 +1,59 @@
import express from 'express'
import { isExecutionQuery } from '../../types'
import path from 'path'
import { getTmpFilesFolderPath, makeFilesNamesMap } from '../../utils'
import { ExecutionController, FileUploadController } from '../../controllers'
const stpRouter = express.Router()
const fileUploadController = new FileUploadController()
stpRouter.post(
'/execute',
fileUploadController.preuploadMiddleware,
fileUploadController.getMulterUploadObject().any(),
async (req: any, res: any) => {
if (isExecutionQuery(req.body)) {
let sasCodePath =
path
.join(getTmpFilesFolderPath(), req.body._program)
.replace(new RegExp('/', 'g'), path.sep) + '.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, ...req.body },
{ filesNamesMap: filesNamesMap },
true
)
.then((result: {}) => {
res.status(200).send({
status: 'success',
...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 stpRouter

View File

@@ -1,176 +0,0 @@
import express from 'express'
import path from 'path'
import {
createFileTree,
getTreeExample,
DriveController,
ExecutionController,
FileUploadController
} from '../controllers'
import { isExecutionQuery, isFileQuery, isFileTree } from '../types'
import {
getTmpFilesFolderPath,
getWebBuildFolderPath,
makeFilesNamesMap
} from '../utils'
const router = express.Router()
const fileUploadController = new FileUploadController()
router.get('/', async (_, res) => {
res.sendFile(path.join(getWebBuildFolderPath(), 'index.html'))
})
router.post('/deploy', async (req, res) => {
if (!isFileTree(req.body.fileTree)) {
res.status(400).send({
status: 'failure',
message: 'Provided not supported data format.',
example: getTreeExample()
})
return
}
await createFileTree(
req.body.fileTree.members,
req.body.appLoc ? req.body.appLoc.replace(/^\//, '').split('/') : []
)
.then(() => {
res.status(200).send({
status: 'success',
message: 'Files deployed successfully to @sasjs/server.'
})
})
.catch((err) => {
res
.status(500)
.send({ status: 'failure', message: 'Deployment failed!', ...err })
})
})
router.get('/SASjsApi/files', async (req, res) => {
if (isFileQuery(req.query)) {
const filePath = path
.join(getTmpFilesFolderPath(), req.query.filePath)
.replace(new RegExp('/', 'g'), path.sep)
await new DriveController()
.readFile(filePath)
.then((fileContent) => {
res.status(200).send({ status: 'success', fileContent: fileContent })
})
.catch((err) => {
res.status(400).send({
status: 'failure',
message: 'File request failed.',
...(typeof err === 'object' ? err : { details: err })
})
})
} else {
res.status(400).send({
status: 'failure',
message: 'Invalid Request: Expected parameter filePath was not provided'
})
}
})
router.post('/SASjsApi/files', async (req, res) => {
const filePath = path
.join(getTmpFilesFolderPath(), req.body.filePath)
.replace(new RegExp('/', 'g'), path.sep)
await new DriveController()
.updateFile(filePath, req.body.fileContent)
.then(() => {
res.status(200).send({ status: 'success' })
})
.catch((err) => {
res.status(400).send({
status: 'failure',
message: 'File request failed.',
...(typeof err === 'object' ? err : { details: err })
})
})
})
router.get('/SASjsApi/executor', async (req, res) => {
const tree = new ExecutionController().buildDirectorytree()
res.status(200).send({ status: 'success', tree })
})
router.get('/SASjsExecutor/do', async (req, res) => {
if (isExecutionQuery(req.query)) {
let sasCodePath = path
.join(getTmpFilesFolderPath(), req.query._program)
.replace(new RegExp('/', 'g'), path.sep)
// If no extension provided, add .sas extension
sasCodePath += '.sas'
await new ExecutionController()
.execute(sasCodePath, undefined, undefined, { ...req.query })
.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`
})
}
})
router.post(
'/SASjsExecutor/do',
fileUploadController.preuploadMiddleware,
fileUploadController.getMulterUploadObject().any(),
async (req: any, res: any) => {
if (isExecutionQuery(req.query)) {
let sasCodePath = path
.join(getTmpFilesFolderPath(), req.query._program)
.replace(new RegExp('/', 'g'), path.sep)
// If no extension provided, add .sas extension
sasCodePath += '.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, ...req.body },
{ 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

View File

@@ -1,103 +0,0 @@
import request from 'supertest'
import app from '../../app'
import { getTreeExample } from '../../controllers/deploy'
import { getTmpFilesFolderPath } from '../../utils/file'
import { folderExists, fileExists, readFile, deleteFolder } from '@sasjs/utils'
import path from 'path'
describe('deploy', () => {
const shouldFailAssertion = async (payload: any) => {
const res = await request(app).post('/deploy').send(payload)
expect(res.statusCode).toEqual(400)
expect(res.body).toEqual({
status: 'failure',
message: 'Provided not supported data format.',
example: getTreeExample()
})
}
it('should respond with payload example if valid payload was not provided', async () => {
await shouldFailAssertion(null)
await shouldFailAssertion(undefined)
await shouldFailAssertion('data')
await shouldFailAssertion({})
await shouldFailAssertion({
userId: 1,
title: 'test is cool'
})
await shouldFailAssertion({
membersWRONG: []
})
await shouldFailAssertion({
members: {}
})
await shouldFailAssertion({
members: [
{
nameWRONG: 'jobs',
type: 'folder',
members: []
}
]
})
await shouldFailAssertion({
members: [
{
name: 'jobs',
type: 'WRONG',
members: []
}
]
})
await shouldFailAssertion({
members: [
{
name: 'jobs',
type: 'folder',
members: [
{
name: 'extract',
type: 'folder',
members: [
{
name: 'makedata1',
type: 'service',
codeWRONG: '%put Hello World!;'
}
]
}
]
}
]
})
})
it('should respond with payload example if valid payload was not provided', async () => {
const res = await request(app)
.post('/deploy')
.send({ fileTree: getTreeExample() })
expect(res.statusCode).toEqual(200)
expect(res.text).toEqual(
'{"status":"success","message":"Files deployed successfully to @sasjs/server."}'
)
await expect(folderExists(getTmpFilesFolderPath())).resolves.toEqual(true)
const testJobFolder = path.join(getTmpFilesFolderPath(), 'jobs', 'extract')
await expect(folderExists(testJobFolder)).resolves.toEqual(true)
const testJobFile = path.join(
testJobFolder,
getTreeExample().members[0].members[0].members[0].name
)
await expect(fileExists(testJobFile)).resolves.toEqual(true)
await expect(readFile(testJobFile)).resolves.toEqual(
getTreeExample().members[0].members[0].members[0].code
)
await deleteFolder(getTmpFilesFolderPath())
})
})

View File

@@ -0,0 +1,8 @@
import express from 'express'
import webRouter from './web'
const router = express.Router()
router.use('/', webRouter)
export default router

43
api/src/routes/web/web.ts Normal file
View File

@@ -0,0 +1,43 @@
import express from 'express'
import { isExecutionQuery } from '../../types'
import path from 'path'
import {
getTmpFilesFolderPath,
getWebBuildFolderPath,
} from '../../utils'
import { ExecutionController } from '../../controllers'
const webRouter = express.Router()
webRouter.get('/', async (_, res) => {
res.sendFile(path.join(getWebBuildFolderPath(), 'index.html'))
})
webRouter.get('/SASjsExecutor/do', async (req, res) => {
if (isExecutionQuery(req.query)) {
let sasCodePath =
path
.join(getTmpFilesFolderPath(), req.query._program)
.replace(new RegExp('/', 'g'), path.sep) + '.sas'
await new ExecutionController()
.execute(sasCodePath, undefined, undefined, { ...req.query })
.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 webRouter