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

Compare commits

...

23 Commits

Author SHA1 Message Date
Saad Jutt
8b4b4b91ab chore(release): 0.0.46 2022-03-30 00:01:18 +05:00
Muhammad Saad
acb3ae0493 Merge pull request #113 from sasjs/issue-108
feat(drive): GET folder contents API added
2022-03-30 00:00:52 +05:00
Saad Jutt
f48aeb1b0b chore(release): 0.0.45 2022-03-29 23:38:09 +05:00
Saad Jutt
5c0e8e5344 chore: Merge branch 'main' into issue-108 2022-03-29 23:28:03 +05:00
Saad Jutt
0ac9e4af7d feat(drive): GET folder contents API added 2022-03-29 23:27:44 +05:00
Muhammad Saad
ee80f3f968 Merge pull request #112 from sasjs/allanbowe/lst-destination-needs-111
fix: providing a PRINT destination during SAS invocation.
2022-03-29 23:11:32 +05:00
Allan Bowe
7f4201ba85 fix: proving a PRINT destination during SAS invocation.
Closes #111
2022-03-29 16:11:15 +00:00
munja
f830bbc058 chore(release): 0.0.44 2022-03-29 11:25:00 +01:00
Allan Bowe
f8e1522a5a Merge pull request #110 from sasjs/issue-108
fix: increased req body size
2022-03-28 14:22:44 +03:00
Saad Jutt
0a5aeceab5 fix: DELETE req cannot have body 2022-03-28 05:05:03 +05:00
Saad Jutt
6dc39c0d91 fix: increased req body size 2022-03-28 04:53:24 +05:00
Saad Jutt
117a53ceea fix(session): increased session + bug fixed 2022-03-24 20:22:06 +05:00
Saad Jutt
dd56a95314 fix(stp): use same session from file upload 2022-03-24 18:06:28 +05:00
Saad Jutt
c5117abe71 chore: README.md updated 2022-03-24 05:56:08 +05:00
Saad Jutt
84c632a861 chore(release): 0.0.43 2022-03-24 04:25:40 +05:00
Muhammad Saad
3ddd09eba0 Merge pull request #105 from sasjs/deploy-app-with-current-names-also
Deploy app with current names also
2022-03-24 04:25:12 +05:00
Saad Jutt
0c0301433c test: fixed 2022-03-24 04:22:30 +05:00
Saad Jutt
954b2e3e2e chore: removed test file 2022-03-24 01:12:04 +05:00
Saad Jutt
5655311b96 fix: fallback logo on AppStream 2022-03-24 01:07:06 +05:00
Saad Jutt
9ace33d783 fix(deploy): user can deploy to same appName with different/same appLoc 2022-03-24 00:54:59 +05:00
Saad Jutt
adc5aca0f0 chore(release): 0.0.42 2022-03-23 22:49:29 +05:00
Muhammad Saad
71c6be6b84 Merge pull request #104 from sasjs/webout-raw
fix: execute api, webout as raw
2022-03-23 22:47:07 +05:00
Saad Jutt
9c751877d1 fix: execute api, webout as raw 2022-03-23 22:41:02 +05:00
22 changed files with 410 additions and 98 deletions

View File

@@ -2,6 +2,49 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.0.46](https://github.com/sasjs/server/compare/v0.0.45...v0.0.46) (2022-03-29)
### Features
* **drive:** GET folder contents API added ([0ac9e4a](https://github.com/sasjs/server/commit/0ac9e4af7d67c4431053e80eb2384bf5bdc3f8b3))
### [0.0.45](https://github.com/sasjs/server/compare/v0.0.43...v0.0.45) (2022-03-29)
### Bug Fixes
* DELETE req cannot have body ([0a5aece](https://github.com/sasjs/server/commit/0a5aeceab560b022197d0c30c3da7f091b261b1e))
* increased req body size ([6dc39c0](https://github.com/sasjs/server/commit/6dc39c0d91ac13d6d9b8c0a2240446bfc45bdd7f))
* proving a PRINT destination during SAS invocation. ([7f4201b](https://github.com/sasjs/server/commit/7f4201ba855743144fa6d3efac2b11e816d4696e)), closes [#111](https://github.com/sasjs/server/issues/111)
* **session:** increased session + bug fixed ([117a53c](https://github.com/sasjs/server/commit/117a53ceeadf487a6326384ae11c10e98646631f))
* **stp:** use same session from file upload ([dd56a95](https://github.com/sasjs/server/commit/dd56a95314f0b61480489118734e45877e1745ef))
### [0.0.44](https://github.com/sasjs/server/compare/v0.0.43...v0.0.44) (2022-03-29)
### Bug Fixes
* DELETE req cannot have body ([0a5aece](https://github.com/sasjs/server/commit/0a5aeceab560b022197d0c30c3da7f091b261b1e))
* increased req body size ([6dc39c0](https://github.com/sasjs/server/commit/6dc39c0d91ac13d6d9b8c0a2240446bfc45bdd7f))
* **session:** increased session + bug fixed ([117a53c](https://github.com/sasjs/server/commit/117a53ceeadf487a6326384ae11c10e98646631f))
* **stp:** use same session from file upload ([dd56a95](https://github.com/sasjs/server/commit/dd56a95314f0b61480489118734e45877e1745ef))
### [0.0.43](https://github.com/sasjs/server/compare/v0.0.42...v0.0.43) (2022-03-23)
### Bug Fixes
* **deploy:** user can deploy to same appName with different/same appLoc ([9ace33d](https://github.com/sasjs/server/commit/9ace33d7830a9def42d741c23b46090afe0c5510))
* fallback logo on AppStream ([5655311](https://github.com/sasjs/server/commit/5655311b9663225823c192b39a03f39d17dda730))
### [0.0.42](https://github.com/sasjs/server/compare/v0.0.41...v0.0.42) (2022-03-23)
### Bug Fixes
* execute api, webout as raw ([9c75187](https://github.com/sasjs/server/commit/9c751877d1ed0d0677aff816169a1df7c34c6bf5))
### [0.0.41](https://github.com/sasjs/server/compare/v0.0.40...v0.0.41) (2022-03-23)

View File

@@ -48,11 +48,21 @@ When launching the app, it will make use of specific environment variables. Thes
Example contents of a `.env` file:
```
MODE=desktop # options: [desktop|server] default: `desktop`
CORS=disable # options: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
WHITELIST= # options: <http://localhost:3000 https://abc.com ...> space separated urls
PROTOCOL=http # options: [http|https] default: http
PORT=5000 # default: 5000
# options: [desktop|server] default: `desktop`
MODE=
# options: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
CORS=
# options: <http://localhost:3000 https://abc.com ...> space separated urls
WHITELIST=
# options: [http|https] default: http
PROTOCOL=
# default: 5000
PORT=
# optional
# for MODE: `desktop`, prompts user

View File

@@ -612,7 +612,6 @@ paths:
responses:
'204':
description: 'No content'
description: "It's optional to either provide `_filePath` in url as query parameter\nOr provide `filePath` in body as form field.\nBut it's required to provide else API will respond with Bad Request."
summary: 'Get file from SASjs Drive'
tags:
- Drive
@@ -623,19 +622,10 @@ paths:
-
in: query
name: _filePath
required: false
required: true
schema:
type: string
example: /Public/somefolder/some.file
requestBody:
required: false
content:
multipart/form-data:
schema:
type: object
properties:
filePath:
type: string
delete:
operationId: DeleteFile
responses:
@@ -649,7 +639,6 @@ paths:
required:
- status
type: object
description: "It's optional to either provide `_filePath` in url as query parameter\nOr provide `filePath` in body as form field.\nBut it's required to provide else API will respond with Bad Request."
summary: 'Delete file from SASjs Drive'
tags:
- Drive
@@ -660,19 +649,10 @@ paths:
-
in: query
name: _filePath
required: false
required: true
schema:
type: string
example: /Public/somefolder/some.file
requestBody:
required: false
content:
multipart/form-data:
schema:
type: object
properties:
filePath:
type: string
post:
operationId: SaveFile
responses:
@@ -775,6 +755,36 @@ paths:
type: string
required:
- file
/SASjsApi/drive/folder:
get:
operationId: GetFolder
responses:
'200':
description: Ok
content:
application/json:
schema:
properties:
folders: {items: {type: string}, type: array}
files: {items: {type: string}, type: array}
required:
- folders
- files
type: object
summary: 'Get folder contents from SASjs Drive'
tags:
- Drive
security:
-
bearerAuth: []
parameters:
-
in: query
name: _folderPath
required: false
schema:
type: string
example: /Public/somefolder
/SASjsApi/drive/filetree:
get:
operationId: GetFileTree

View File

@@ -10,8 +10,8 @@ import {
copySASjsCore,
getWebBuildFolderPath,
loadAppStreamConfig,
sasJSCoreMacros,
setProcessVariables
setProcessVariables,
setupFolders
} from './utils'
dotenv.config()
@@ -34,7 +34,7 @@ if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') {
app.use(cookieParser())
app.use(morgan('tiny'))
app.use(express.json({ limit: '50mb' }))
app.use(express.json({ limit: '100mb' }))
app.use(express.static(path.join(__dirname, '../public')))
const onError: ErrorRequestHandler = (err, req, res, next) => {
@@ -43,6 +43,7 @@ const onError: ErrorRequestHandler = (err, req, res, next) => {
}
export default setProcessVariables().then(async () => {
await setupFolders()
await copySASjsCore()
// loading these modules after setting up variables due to

View File

@@ -20,7 +20,12 @@ import {
fileExists,
moveFile,
createFolder,
deleteFile as deleteFileOnSystem
deleteFile as deleteFileOnSystem,
folderExists,
listFilesAndSubFoldersInFolder,
listFilesInFolder,
listSubFoldersInFolder,
isFolder
} from '@sasjs/utils'
import { createFileTree, ExecutionController, getTreeExample } from './internal'
@@ -89,9 +94,6 @@ export class DriveController {
}
/**
* It's optional to either provide `_filePath` in url as query parameter
* Or provide `filePath` in body as form field.
* But it's required to provide else API will respond with Bad Request.
*
* @summary Get file from SASjs Drive
* @query _filePath Location of SAS program
@@ -100,28 +102,31 @@ export class DriveController {
@Get('/file')
public async getFile(
@Request() request: express.Request,
@Query() _filePath?: string,
@FormField() filePath?: string
@Query() _filePath: string
) {
return getFile(request, (_filePath ?? filePath)!)
return getFile(request, _filePath)
}
/**
*
* @summary Get folder contents from SASjs Drive
* @query _folderPath Location of SAS program
* @example _folderPath "/Public/somefolder"
*/
@Get('/folder')
public async getFolder(@Query() _folderPath?: string) {
return getFolder(_folderPath)
}
/**
* It's optional to either provide `_filePath` in url as query parameter
* Or provide `filePath` in body as form field.
* But it's required to provide else API will respond with Bad Request.
*
* @summary Delete file from SASjs Drive
* @query _filePath Location of SAS program
* @example _filePath "/Public/somefolder/some.file"
*/
@Delete('/file')
public async deleteFile(
@Query() _filePath?: string,
@FormField() filePath?: string
) {
return deleteFile((_filePath ?? filePath)!)
public async deleteFile(@Query() _filePath: string) {
return deleteFile(_filePath)
}
/**
@@ -227,7 +232,7 @@ const getFile = async (req: express.Request, filePath: string) => {
}
if (!(await fileExists(filePathFull))) {
throw new Error('File does not exist.')
throw new Error("File doesn't exist.")
}
const extension = path.extname(filePathFull).toLowerCase()
@@ -238,6 +243,36 @@ const getFile = async (req: express.Request, filePath: string) => {
req.res?.sendFile(path.resolve(filePathFull))
}
const getFolder = async (folderPath?: string) => {
const driveFilesPath = getTmpFilesFolderPath()
if (folderPath) {
const folderPathFull = path
.join(getTmpFilesFolderPath(), folderPath)
.replace(new RegExp('/', 'g'), path.sep)
if (!folderPathFull.includes(driveFilesPath)) {
throw new Error('Cannot get folder outside drive.')
}
if (!(await folderExists(folderPathFull))) {
throw new Error("Folder doesn't exist.")
}
if (!(await isFolder(folderPathFull))) {
throw new Error('Not a Folder.')
}
const files: string[] = await listFilesInFolder(folderPathFull)
const folders: string[] = await listSubFoldersInFolder(folderPathFull)
return { files, folders }
}
const files: string[] = await listFilesInFolder(driveFilesPath)
const folders: string[] = await listSubFoldersInFolder(driveFilesPath)
return { files, folders }
}
const deleteFile = async (filePath: string) => {
const driveFilesPath = getTmpFilesFolderPath()
@@ -305,9 +340,3 @@ const updateFile = async (
return { status: 'success' }
}
const validateFilePath = async (filePath: string) => {
if (!(await fileExists(filePath))) {
throw 'DriveController: File does not exists.'
}
}

View File

@@ -8,7 +8,7 @@ import {
moveFile,
readFileBinary
} from '@sasjs/utils'
import { PreProgramVars, TreeNode } from '../../types'
import { PreProgramVars, Session, TreeNode } from '../../types'
import {
extractHeaders,
generateFileUploadSasCode,
@@ -39,7 +39,8 @@ export class ExecutionController {
preProgramVariables: PreProgramVars,
vars: ExecutionVars,
otherArgs?: any,
returnJson?: boolean
returnJson?: boolean,
session?: Session
) {
if (!(await fileExists(programPath)))
throw 'ExecutionController: SAS file does not exist.'
@@ -51,7 +52,8 @@ export class ExecutionController {
preProgramVariables,
vars,
otherArgs,
returnJson
returnJson,
session
)
}
@@ -60,11 +62,13 @@ export class ExecutionController {
preProgramVariables: PreProgramVars,
vars: ExecutionVars,
otherArgs?: any,
returnJson?: boolean
returnJson?: boolean,
sessionByFileUpload?: Session
): Promise<ExecuteReturnRaw | ExecuteReturnJson> {
const sessionController = getSessionController()
const session = await sessionController.getSession()
const session =
sessionByFileUpload ?? (await sessionController.getSession())
session.inUse = true
session.consumed = true

View File

@@ -23,7 +23,9 @@ export class FileUploadController {
const sessionController = getSessionController()
session = await sessionController.getSession()
session.inUse = true
// marking consumed true, so that it's not available
// as readySession for any other request
session.consumed = true
req.sasSession = session

View File

@@ -31,7 +31,7 @@ export class SessionController {
? readySessions[0]
: await this.createSession()
if (readySessions.length < 2) this.createSession()
if (readySessions.length < 3) this.createSession()
return session
}
@@ -87,6 +87,8 @@ ${autoExecContent}`
codePath,
'-LOG',
path.join(session.path, 'log.log'),
'-PRINT',
path.join(session.path, 'output.lst'),
'-WORK',
session.path,
'-AUTOEXEC',

View File

@@ -143,9 +143,8 @@ const executeReturnRaw = async (
query
)) as ExecuteReturnRaw
// Should over-ride response header for
// debug on GET request to see entire log
// rendering on browser.
// Should over-ride response header for debug
// on GET request to see entire log rendering on browser.
if (isDebugOn(query)) {
httpHeaders['content-type'] = 'text/plain'
}
@@ -185,7 +184,8 @@ const executeReturnJson = async (
getPreProgramVariables(req),
{ ...req.query, ...req.body },
{ filesNamesMap: filesNamesMap },
true
true,
req.sasSession
)) as ExecuteReturnJson
let weboutRes: string | IRecordOfAny = webout

View File

@@ -8,7 +8,8 @@ import { DriveController } from '../../controllers/'
import {
deployValidation,
fileBodyValidation,
fileParamValidation
fileParamValidation,
folderParamValidation
} from '../../utils'
const controller = new DriveController()
@@ -44,12 +45,24 @@ driveRouter.post('/deploy', async (req, res) => {
driveRouter.get('/file', async (req, res) => {
const { error: errQ, value: query } = fileParamValidation(req.query)
const { error: errB, value: body } = fileBodyValidation(req.body)
if (errQ && errB) return res.status(400).send(errQ.details[0].message)
if (errQ) return res.status(400).send(errQ.details[0].message)
try {
await controller.getFile(req, query._filePath, body.filePath)
await controller.getFile(req, query._filePath)
} catch (err: any) {
res.status(403).send(err.toString())
}
})
driveRouter.get('/folder', async (req, res) => {
const { error: errQ, value: query } = folderParamValidation(req.query)
if (errQ) return res.status(400).send(errQ.details[0].message)
try {
const response = await controller.getFolder(query._folderPath)
res.send(response)
} catch (err: any) {
res.status(403).send(err.toString())
}
@@ -57,12 +70,11 @@ driveRouter.get('/file', async (req, res) => {
driveRouter.delete('/file', async (req, res) => {
const { error: errQ, value: query } = fileParamValidation(req.query)
const { error: errB, value: body } = fileBodyValidation(req.body)
if (errQ && errB) return res.status(400).send(errQ.details[0].message)
if (errQ) return res.status(400).send(errQ.details[0].message)
try {
const response = await controller.deleteFile(query._filePath, body.filePath)
const response = await controller.deleteFile(query._filePath)
res.send(response)
} catch (err: any) {
res.status(403).send(err.toString())

View File

@@ -10,7 +10,9 @@ import {
readFile,
deleteFolder,
generateTimestamp,
copy
copy,
createFolder,
createFile
} from '@sasjs/utils'
import * as fileUtilModules from '../../../utils/file'
@@ -44,7 +46,7 @@ const user = {
isActive: true
}
describe('files', () => {
describe('drive', () => {
let con: Mongoose
let mongoServer: MongoMemoryServer
const controller = new UserController()
@@ -69,6 +71,7 @@ describe('files', () => {
await mongoServer.stop()
await deleteFolder(tmpFolder)
})
describe('deploy', () => {
const shouldFailAssertion = async (payload: any) => {
const res = await request(app)
@@ -172,17 +175,126 @@ describe('files', () => {
await expect(readFile(testJobFile)).resolves.toEqual(exampleService.code)
await deleteFolder(getTmpFilesFolderPath())
await deleteFolder(path.join(getTmpFilesFolderPath(), 'public'))
})
})
describe('folder', () => {
describe('get', () => {
const getFolderApi = '/SASjsApi/drive/folder'
it('should get root SAS folder on drive', async () => {
const res = await request(app)
.get(getFolderApi)
.auth(accessToken, { type: 'bearer' })
expect(res.statusCode).toEqual(200)
expect(res.body).toEqual({ files: [], folders: [] })
})
it('should get a SAS folder on drive having _folderPath as query param', async () => {
const pathToDrive = fileUtilModules.getTmpFilesFolderPath()
const dirLevel1 = 'level1'
const dirLevel2 = 'level2'
const fileLevel1 = 'file1'
const fileLevel2 = 'file2'
await createFolder(path.join(pathToDrive, dirLevel1, dirLevel2))
await createFile(
path.join(pathToDrive, dirLevel1, fileLevel1),
'some file content'
)
await createFile(
path.join(pathToDrive, dirLevel1, dirLevel2, fileLevel2),
'some file content'
)
const res1 = await request(app)
.get(getFolderApi)
.query({ _folderPath: '/' })
.auth(accessToken, { type: 'bearer' })
expect(res1.statusCode).toEqual(200)
expect(res1.body).toEqual({ files: [], folders: [dirLevel1] })
const res2 = await request(app)
.get(getFolderApi)
.query({ _folderPath: dirLevel1 })
.auth(accessToken, { type: 'bearer' })
expect(res2.statusCode).toEqual(200)
expect(res2.body).toEqual({ files: [fileLevel1], folders: [dirLevel2] })
const res3 = await request(app)
.get(getFolderApi)
.query({ _folderPath: `${dirLevel1}/${dirLevel2}` })
.auth(accessToken, { type: 'bearer' })
expect(res3.statusCode).toEqual(200)
expect(res3.body).toEqual({ files: [fileLevel2], folders: [] })
})
it('should respond with Unauthorized if access token is not present', async () => {
const res = await request(app).get(getFolderApi).expect(401)
expect(res.text).toEqual('Unauthorized')
expect(res.body).toEqual({})
})
it('should respond with Forbidden if folder is not present', async () => {
const res = await request(app)
.get(getFolderApi)
.auth(accessToken, { type: 'bearer' })
.query({ _folderPath: `/my/path/code-${generateTimestamp()}` })
.expect(403)
expect(res.text).toEqual(`Error: Folder doesn't exist.`)
expect(res.body).toEqual({})
})
it('should respond with Forbidden if folderPath outside Drive', async () => {
const res = await request(app)
.get(getFolderApi)
.auth(accessToken, { type: 'bearer' })
.query({ _folderPath: '/../path/code.sas' })
.expect(403)
expect(res.text).toEqual('Error: Cannot get folder outside drive.')
expect(res.body).toEqual({})
})
it('should respond with Forbidden if folderPath is of a file', async () => {
const fileToCopyPath = path.join(__dirname, 'files', 'sample.sas')
const filePath = '/my/path/code.sas'
const pathToCopy = path.join(
fileUtilModules.getTmpFilesFolderPath(),
filePath
)
await copy(fileToCopyPath, pathToCopy)
const res = await request(app)
.get(getFolderApi)
.auth(accessToken, { type: 'bearer' })
.query({ _folderPath: filePath })
.expect(403)
expect(res.text).toEqual('Error: Not a Folder.')
expect(res.body).toEqual({})
})
})
})
describe('file', () => {
describe('create', () => {
it('should create a SAS file on drive having filePath as form field', async () => {
const pathToUpload = `/my/path/code-1.sas`
const res = await request(app)
.post('/SASjsApi/drive/file')
.auth(accessToken, { type: 'bearer' })
.field('filePath', '/my/path/code.sas')
.field('filePath', pathToUpload)
.attach('file', path.join(__dirname, 'files', 'sample.sas'))
expect(res.statusCode).toEqual(200)
@@ -192,10 +304,12 @@ describe('files', () => {
})
it('should create a SAS file on drive having _filePath as query param', async () => {
const pathToUpload = `/my/path/code-2.sas`
const res = await request(app)
.post('/SASjsApi/drive/file')
.auth(accessToken, { type: 'bearer' })
.query({ _filePath: '/my/path/code1.sas' })
.query({ _filePath: pathToUpload })
.attach('file', path.join(__dirname, 'files', 'sample.sas'))
expect(res.statusCode).toEqual(200)
@@ -217,7 +331,7 @@ describe('files', () => {
it('should respond with Forbidden if file is already present', async () => {
const fileToAttachPath = path.join(__dirname, 'files', 'sample.sas')
const pathToUpload = '/my/path/code.sas'
const pathToUpload = `/my/path/code-${generateTimestamp()}.sas`
const pathToCopy = path.join(
fileUtilModules.getTmpFilesFolderPath(),
@@ -386,7 +500,7 @@ describe('files', () => {
const res = await request(app)
.patch('/SASjsApi/drive/file')
.auth(accessToken, { type: 'bearer' })
.field('filePath', `/my/path/code-${generateTimestamp()}.sas`)
.field('filePath', `/my/path/code-3.sas`)
.attach('file', path.join(__dirname, 'files', 'sample.sas'))
.expect(403)
@@ -427,9 +541,9 @@ describe('files', () => {
const pathToUpload = '/my/path/code.exe'
const res = await request(app)
.patch(`/SASjsApi/drive/file?_filePath=${pathToUpload}`)
.patch('/SASjsApi/drive/file')
.auth(accessToken, { type: 'bearer' })
// .field('filePath', pathToUpload)
.query({ _filePath: pathToUpload })
.attach('file', fileToAttachPath)
.expect(400)
@@ -483,6 +597,79 @@ describe('files', () => {
expect(res.body).toEqual({})
})
})
describe('get', () => {
it('should get a SAS file on drive having _filePath as query param', async () => {
const fileToCopyPath = path.join(__dirname, 'files', 'sample.sas')
const fileToCopyContent = await readFile(fileToCopyPath)
const filePath = '/my/path/code.sas'
const pathToCopy = path.join(
fileUtilModules.getTmpFilesFolderPath(),
filePath
)
await copy(fileToCopyPath, pathToCopy)
const res = await request(app)
.get('/SASjsApi/drive/file')
.auth(accessToken, { type: 'bearer' })
.query({ _filePath: filePath })
expect(res.statusCode).toEqual(200)
expect(res.body).toEqual({})
expect(res.text).toEqual(fileToCopyContent)
})
it('should respond with Unauthorized if access token is not present', async () => {
const res = await request(app).get('/SASjsApi/drive/file').expect(401)
expect(res.text).toEqual('Unauthorized')
expect(res.body).toEqual({})
})
it('should respond with Forbidden if file is not present', async () => {
const res = await request(app)
.get('/SASjsApi/drive/file')
.auth(accessToken, { type: 'bearer' })
.query({ _filePath: `/my/path/code-4.sas` })
.expect(403)
expect(res.text).toEqual(`Error: File doesn't exist.`)
expect(res.body).toEqual({})
})
it('should respond with Forbidden if filePath outside Drive', async () => {
const res = await request(app)
.get('/SASjsApi/drive/file')
.auth(accessToken, { type: 'bearer' })
.query({ _filePath: '/../path/code.sas' })
.expect(403)
expect(res.text).toEqual('Error: Cannot get file outside drive.')
expect(res.body).toEqual({})
})
it("should respond with Bad Request if filePath doesn't has correct extension", async () => {
const res = await request(app)
.patch('/SASjsApi/drive/file')
.auth(accessToken, { type: 'bearer' })
.query({ _filePath: '/my/path/code.exe' })
.expect(400)
expect(res.text).toEqual('Invalid file extension')
expect(res.body).toEqual({})
})
it('should respond with Bad Request if filePath is missing', async () => {
const res = await request(app)
.post('/SASjsApi/drive/file')
.auth(accessToken, { type: 'bearer' })
.expect(400)
expect(res.text).toEqual(`"_filePath" is required`)
expect(res.body).toEqual({})
})
})
})
})

View File

@@ -14,12 +14,12 @@ const style = `<style>
width: 150px;
margin: 10px;
overflow: hidden;
border-radius: 10px 10px 0 0;
text-align: center;
}
.app-container .app img{
width: 100%;
margin-bottom: 10px;
border-radius: 10px;
}
</style>`
@@ -31,7 +31,10 @@ const singleAppStreamHtml = (
logo?: string
) =>
` <a class="app" href="${streamServiceName}" title="${appLoc}">
<img src="${logo ? streamServiceName + '/' + logo : defaultAppLogo}" />
<img
src="${logo ? streamServiceName + '/' + logo : defaultAppLogo}"
onerror="this.src = '${defaultAppLogo}';"
/>
${streamServiceName}
</a>`

View File

@@ -40,17 +40,6 @@ export const publishAppStream = async (
if (!streamServiceName) {
streamServiceName = `AppStreamName${appCount + 1}`
} else {
const alreadyDeployed = process.appStreamConfig[streamServiceName]
if (alreadyDeployed) {
if (alreadyDeployed.appLoc === appLoc) {
// redeploying to same streamServiceName
} else {
// trying to deploy to another existing streamServiceName
// assign new streamServiceName
streamServiceName = `${streamServiceName}-${appCount + 1}`
}
}
}
router.use(`/${streamServiceName}`, express.static(pathToDeployment))

View File

@@ -5,6 +5,8 @@ import { AppStreamConfig } from '../types'
import { getTmpAppStreamConfigPath } from './file'
export const loadAppStreamConfig = async () => {
if (process.env.NODE_ENV === 'test') return
const appStreamConfigPath = getTmpAppStreamConfigPath()
const content = (await fileExists(appStreamConfigPath))

View File

@@ -10,6 +10,8 @@ import {
import { getTmpMacrosPath, sasJSCoreMacros, sasJSCoreMacrosInfo } from '.'
export const copySASjsCore = async () => {
if (process.env.NODE_ENV === 'test') return
console.log('Copying Macros from container to drive(tmp).')
const macrosDrivePath = getTmpMacrosPath()

View File

@@ -13,6 +13,7 @@ export * from './parseLogToArray'
export * from './removeTokensInDB'
export * from './saveTokensInDB'
export * from './setProcessVariables'
export * from './setupFolders'
export * from './sleep'
export * from './upload'
export * from './validation'

View File

@@ -0,0 +1,7 @@
import { createFolder } from '@sasjs/utils'
import { getTmpFilesFolderPath } from './file'
export const setupFolders = async () => {
const drivePath = getTmpFilesFolderPath()
await createFolder(drivePath)
}

View File

@@ -98,6 +98,11 @@ export const fileParamValidation = (data: any): Joi.ValidationResult =>
_filePath: filePathSchema
}).validate(data)
export const folderParamValidation = (data: any): Joi.ValidationResult =>
Joi.object({
_folderPath: Joi.string()
}).validate(data)
export const runSASValidation = (data: any): Joi.ValidationResult =>
Joi.object({
code: Joi.string().required()

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "server",
"version": "0.0.41",
"version": "0.0.46",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "server",
"version": "0.0.41",
"version": "0.0.46",
"devDependencies": {
"prettier": "^2.3.1",
"standard-version": "^9.3.2"

View File

@@ -1,6 +1,6 @@
{
"name": "server",
"version": "0.0.41",
"version": "0.0.46",
"description": "NodeJS wrapper for calling the SAS binary executable",
"repository": "https://github.com/sasjs/server",
"scripts": {

View File

@@ -1,3 +1,6 @@
### Get contents of folder
GET http://localhost:5000/SASjsApi/drive/folder?_path=/Public/app/react-seed-app/services/web
###
POST http://localhost:5000/SASjsApi/drive/deploy
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiJjbGllbnRJRDEiLCJ1c2VybmFtZSI6InVzZXJuYW1lMSIsImlzYWRtaW4iOmZhbHNlLCJpc2FjdGl2ZSI6dHJ1ZSwiaWF0IjoxNjM1ODA0MDc2LCJleHAiOjE2MzU4OTA0NzZ9.Cx1F54ILgAUtnkit0Wg1K1YVO2RdNjOnTKdPhUtDm5I

View File

@@ -54,11 +54,11 @@ const Studio = () => {
let weboutString: string
try {
weboutString = res.data.webout
weboutString = res.data._webout
.split('>>weboutBEGIN<<')[1]
.split('>>weboutEND<<')[0]
} catch (_) {
weboutString = res?.data?.webout ?? ''
weboutString = res?.data?._webout ?? ''
}
let webout: string