diff --git a/api/src/app.ts b/api/src/app.ts index aebf051..28c31d1 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -1,11 +1,16 @@ import express from 'express' -import indexRouter from './routes' -import path from 'path' +import webRouter from './routes/web' +import apiRouter from './routes/api' import { getWebBuildFolderPath } from './utils' + const app = express() 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())) export default app diff --git a/api/src/controllers/Execution.ts b/api/src/controllers/Execution.ts index a025dd4..76378b2 100644 --- a/api/src/controllers/Execution.ts +++ b/api/src/controllers/Execution.ts @@ -16,7 +16,8 @@ export class ExecutionController { autoExec?: string, session?: Session, vars?: any, - otherArgs?: any + otherArgs?: any, + returnJson?: boolean ) { if (program) { if (!(await fileExists(program))) { @@ -91,6 +92,7 @@ ${program}` (key: string) => key.toLowerCase() === '_debug' ) + let jsonResult if ((debug && vars[debug] >= 131) || stderr) { webout = `
${webout} @@ -99,13 +101,15 @@ ${webout}${log}
`
+ } else if (returnJson) {
+ jsonResult = { result: webout, log: log }
}
session.inUse = false
sessionController.deleteSession(session)
- return Promise.resolve(webout)
+ return Promise.resolve(jsonResult || webout)
}
buildDirectorytree() {
diff --git a/api/src/controllers/deploy.ts b/api/src/controllers/deploy.ts
index 2c1f1db..a3fb302 100644
--- a/api/src/controllers/deploy.ts
+++ b/api/src/controllers/deploy.ts
@@ -14,7 +14,9 @@ export const createFileTree = async (
)
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) {
await createFolder(path.join(destinationPath, name)).catch((err) =>
diff --git a/api/src/routes/api/drive.ts b/api/src/routes/api/drive.ts
new file mode 100644
index 0000000..986c677
--- /dev/null
+++ b/api/src/routes/api/drive.ts
@@ -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
diff --git a/api/src/routes/api/index.ts b/api/src/routes/api/index.ts
new file mode 100644
index 0000000..afaa0d6
--- /dev/null
+++ b/api/src/routes/api/index.ts
@@ -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
diff --git a/api/src/routes/api/spec/drive.spec.ts b/api/src/routes/api/spec/drive.spec.ts
new file mode 100644
index 0000000..191db5b
--- /dev/null
+++ b/api/src/routes/api/spec/drive.spec.ts
@@ -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())
+ })
+ })
+})
diff --git a/api/src/routes/api/stp.ts b/api/src/routes/api/stp.ts
new file mode 100644
index 0000000..3c8e597
--- /dev/null
+++ b/api/src/routes/api/stp.ts
@@ -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
diff --git a/api/src/routes/index.ts b/api/src/routes/index.ts
deleted file mode 100644
index dab165e..0000000
--- a/api/src/routes/index.ts
+++ /dev/null
@@ -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
diff --git a/api/src/routes/spec/routes.spec.ts b/api/src/routes/spec/routes.spec.ts
deleted file mode 100644
index 56d2e69..0000000
--- a/api/src/routes/spec/routes.spec.ts
+++ /dev/null
@@ -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())
- })
-})
diff --git a/api/src/routes/web/index.ts b/api/src/routes/web/index.ts
new file mode 100644
index 0000000..6f75c11
--- /dev/null
+++ b/api/src/routes/web/index.ts
@@ -0,0 +1,8 @@
+import express from 'express'
+import webRouter from './web'
+
+const router = express.Router()
+
+router.use('/', webRouter)
+
+export default router
diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts
new file mode 100644
index 0000000..7f90ea6
--- /dev/null
+++ b/api/src/routes/web/web.ts
@@ -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