diff --git a/src/app.ts b/src/app.ts index 93db9fe..646ad88 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,10 +1,12 @@ import express from 'express' -import indexRouter from './routes' +import webRouter from './routes/web' +import apiRouter from './routes/api' const app = express() app.use(express.json({ limit: '50mb' })) -app.use('/', indexRouter) +app.use('/', webRouter) +app.use('/SASjsApi', apiRouter) export default app diff --git a/src/controllers/Execution.ts b/src/controllers/Execution.ts index 0410594..d57ffc0 100644 --- a/src/controllers/Execution.ts +++ b/src/controllers/Execution.ts @@ -14,7 +14,8 @@ export class ExecutionController { autoExec?: string, session?: Session, vars?: any, - otherArgs?: any + otherArgs?: any, + returnJson?: boolean ) { if (program) { if (!(await fileExists(program))) { @@ -89,6 +90,7 @@ ${program}` (key: string) => key.toLowerCase() === '_debug' ) + let jsonResult if ((debug && vars[debug] >= 131) || stderr) { webout = `
${webout} @@ -97,12 +99,14 @@ ${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)
}
}
diff --git a/src/controllers/deploy.ts b/src/controllers/deploy.ts
index 2c1f1db..a3fb302 100644
--- a/src/controllers/deploy.ts
+++ b/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/src/routes/api/drive.ts b/src/routes/api/drive.ts
new file mode 100644
index 0000000..c3724ab
--- /dev/null
+++ b/src/routes/api/drive.ts
@@ -0,0 +1,35 @@
+import express from 'express'
+import { createFileTree, getTreeExample } from '../../controllers'
+import { isFileTree } from '../../types'
+
+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 })
+ })
+})
+
+export default driveRouter
diff --git a/src/routes/api/index.ts b/src/routes/api/index.ts
new file mode 100644
index 0000000..afaa0d6
--- /dev/null
+++ b/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/src/routes/api/spec/drive.spec.ts b/src/routes/api/spec/drive.spec.ts
new file mode 100644
index 0000000..191db5b
--- /dev/null
+++ b/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/src/routes/api/stp.ts b/src/routes/api/stp.ts
new file mode 100644
index 0000000..45ae3a1
--- /dev/null
+++ b/src/routes/api/stp.ts
@@ -0,0 +1,59 @@
+import express from 'express'
+import { ExecutionResult, isRequestQuery, isFileTree } 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 (isRequestQuery(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/src/routes/spec/routes.spec.ts b/src/routes/spec/routes.spec.ts
deleted file mode 100644
index 56d2e69..0000000
--- a/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/src/routes/web/index.ts b/src/routes/web/index.ts
new file mode 100644
index 0000000..21fcb3e
--- /dev/null
+++ b/src/routes/web/index.ts
@@ -0,0 +1,8 @@
+import express from 'express'
+import webRouter from './web'
+
+const router = express.Router()
+
+router.use('/web', webRouter)
+
+export default router
diff --git a/src/routes/web/web.ts b/src/routes/web/web.ts
new file mode 100644
index 0000000..c45f67b
--- /dev/null
+++ b/src/routes/web/web.ts
@@ -0,0 +1,55 @@
+import express from 'express'
+import {
+ createFileTree,
+ getSessionController,
+ getTreeExample
+} from '../../controllers'
+import { ExecutionResult, isRequestQuery, isFileTree } from '../../types'
+import path from 'path'
+import {
+ getTmpFilesFolderPath,
+ getTmpFolderPath,
+ makeFilesNamesMap
+} from '../../utils'
+import { ExecutionController, FileUploadController } from '../../controllers'
+import { uuidv4 } from '@sasjs/utils'
+
+const webRouter = express.Router()
+
+webRouter.get('/', async (_, res) => {
+ res.status(200).send('Welcome to @sasjs/server API')
+})
+
+// TODO: respond with HTML page including file tree
+webRouter.get('/SASjsExecutor', async (req, res) => {
+ res.status(200).send({ status: 'success', tree: {} })
+})
+
+webRouter.get('/SASjsExecutor/do', async (req, res) => {
+ if (isRequestQuery(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