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

Compare commits

...

34 Commits

Author SHA1 Message Date
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
Saad Jutt
2204d54cd6 chore(release): 0.0.41 2022-03-23 21:32:06 +05:00
Saad Jutt
f4eb75ff34 fix(scroll): closes #100 2022-03-23 21:31:54 +05:00
Saad Jutt
a3cde343b7 chore(release): 0.0.40 2022-03-23 20:17:15 +05:00
Saad Jutt
7a70d40dbf fix: macros available for SAS 2022-03-23 20:15:18 +05:00
Saad Jutt
d27e070fc8 fix: moved macros from codebase to drive
This reverts commit d2956fc641.
2022-03-23 19:38:29 +05:00
Saad Jutt
27e260e6a4 fix(deploy): validating empty file or service in filetree 2022-03-23 19:38:20 +05:00
Saad Jutt
2796db8ead chore(release): 0.0.39 2022-03-23 18:07:12 +05:00
Muhammad Saad
84f7c2ab89 Merge pull request #103 from sasjs/executable-macros-fix
Executable macros fix
2022-03-23 18:07:01 +05:00
Saad Jutt
e68090181a fix: included sasjs core macros at compile time 2022-03-23 18:05:03 +05:00
Saad Jutt
d2956fc641 Revert "fix: moved macros from codebase to drive"
This reverts commit 9ac3191891.
2022-03-23 17:59:06 +05:00
Saad Jutt
a701bb25e7 Revert "fix: quick fix for executables"
This reverts commit 9e53470947.
2022-03-23 17:58:18 +05:00
Saad Jutt
5758bcd392 chore(release): 0.0.38 2022-03-23 17:16:01 +05:00
Saad Jutt
9e53470947 fix: quick fix for executables 2022-03-23 17:15:46 +05:00
Saad Jutt
81f6605249 chore(release): 0.0.37 2022-03-23 09:23:20 +05:00
Muhammad Saad
0b45402946 Merge pull request #102 from sasjs/issue-95
fix: moved macros from codebase to drive
2022-03-23 09:23:01 +05:00
Saad Jutt
9ac3191891 fix: moved macros from codebase to drive 2022-03-23 09:19:33 +05:00
Saad Jutt
cd00aa2af8 fix: appStream html view 2022-03-22 21:52:39 +05:00
Saad Jutt
0147bcb701 fix(webin): closes #99 2022-03-22 21:28:31 +05:00
25 changed files with 303 additions and 161 deletions

View File

@@ -2,6 +2,70 @@
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. 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.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)
### Bug Fixes
* **scroll:** closes [#100](https://github.com/sasjs/server/issues/100) ([f4eb75f](https://github.com/sasjs/server/commit/f4eb75ff347e78ac334e55ee26fbdd247bb8eaa2))
### [0.0.40](https://github.com/sasjs/server/compare/v0.0.39...v0.0.40) (2022-03-23)
### Bug Fixes
* **deploy:** validating empty file or service in filetree ([27e260e](https://github.com/sasjs/server/commit/27e260e6a453e9978830db63ab669bd48c029897))
* macros available for SAS ([7a70d40](https://github.com/sasjs/server/commit/7a70d40dbf0cd91cb3af156755f10006b860f917))
* moved macros from codebase to drive ([d27e070](https://github.com/sasjs/server/commit/d27e070fc83894854278df22a8223b8016a1f5f7))
### [0.0.39](https://github.com/sasjs/server/compare/v0.0.38...v0.0.39) (2022-03-23)
### Bug Fixes
* included sasjs core macros at compile time ([e680901](https://github.com/sasjs/server/commit/e68090181acd844f86f3e81153cb5a4e3f4a307f))
### [0.0.38](https://github.com/sasjs/server/compare/v0.0.37...v0.0.38) (2022-03-23)
### Bug Fixes
* quick fix for executables ([9e53470](https://github.com/sasjs/server/commit/9e53470947350f4b8d835a2cb6b70e3dabf247c4))
### [0.0.37](https://github.com/sasjs/server/compare/v0.0.36...v0.0.37) (2022-03-23)
### Bug Fixes
* appStream html view ([cd00aa2](https://github.com/sasjs/server/commit/cd00aa2af8c7e0df851050a02152dfeddaec7b0f))
* moved macros from codebase to drive ([9ac3191](https://github.com/sasjs/server/commit/9ac3191891bf53ff07135ccec6ddc83b34ea871a))
* **webin:** closes [#99](https://github.com/sasjs/server/issues/99) ([0147bcb](https://github.com/sasjs/server/commit/0147bcb701a209266144147a3746baf1eb1ccc63))
### [0.0.36](https://github.com/sasjs/server/compare/v0.0.35...v0.0.36) (2022-03-21) ### [0.0.36](https://github.com/sasjs/server/compare/v0.0.35...v0.0.36) (2022-03-21)

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: Example contents of a `.env` file:
``` ```
MODE=desktop # options: [desktop|server] default: `desktop` # options: [desktop|server] default: `desktop`
CORS=disable # options: [disable|enable] default: `disable` for `server` & `enable` for `desktop` MODE=
WHITELIST= # options: <http://localhost:3000 https://abc.com ...> space separated urls
PROTOCOL=http # options: [http|https] default: http # options: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
PORT=5000 # default: 5000 CORS=
# options: <http://localhost:3000 https://abc.com ...> space separated urls
WHITELIST=
# options: [http|https] default: http
PROTOCOL=
# default: 5000
PORT=
# optional # optional
# for MODE: `desktop`, prompts user # for MODE: `desktop`, prompts user

View File

@@ -29,6 +29,7 @@
"assets": [ "assets": [
"./build/public/**/*", "./build/public/**/*",
"./build/sasjsbuild/**/*", "./build/sasjsbuild/**/*",
"./build/sasjscore/**/*",
"./web/build/**/*" "./web/build/**/*"
], ],
"targets": [ "targets": [
@@ -91,7 +92,7 @@
}, },
"nodemonConfig": { "nodemonConfig": {
"ignore": [ "ignore": [
"tmp/appStreamConfig.json" "tmp/**/*"
] ]
} }
} }

View File

@@ -161,6 +161,8 @@ components:
$ref: '#/components/schemas/FolderMember' $ref: '#/components/schemas/FolderMember'
- -
$ref: '#/components/schemas/ServiceMember' $ref: '#/components/schemas/ServiceMember'
-
$ref: '#/components/schemas/FileMember'
type: array type: array
required: required:
- name - name
@@ -172,19 +174,29 @@ components:
enum: enum:
- service - service
type: string type: string
MemberType.file:
enum:
- file
type: string
ServiceMember: ServiceMember:
properties: properties:
name: name:
type: string type: string
type: type:
anyOf:
-
$ref: '#/components/schemas/MemberType.service' $ref: '#/components/schemas/MemberType.service'
- code:
type: string
required:
- name
- type
- code
type: object
additionalProperties: false
MemberType.file:
enum:
- file
type: string
FileMember:
properties:
name:
type: string
type:
$ref: '#/components/schemas/MemberType.file' $ref: '#/components/schemas/MemberType.file'
code: code:
type: string type: string
@@ -203,6 +215,8 @@ components:
$ref: '#/components/schemas/FolderMember' $ref: '#/components/schemas/FolderMember'
- -
$ref: '#/components/schemas/ServiceMember' $ref: '#/components/schemas/ServiceMember'
-
$ref: '#/components/schemas/FileMember'
type: array type: array
required: required:
- members - members
@@ -635,7 +649,6 @@ paths:
required: required:
- status - status
type: object 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' summary: 'Delete file from SASjs Drive'
tags: tags:
- Drive - Drive
@@ -646,19 +659,10 @@ paths:
- -
in: query in: query
name: _filePath name: _filePath
required: false required: true
schema: schema:
type: string type: string
example: /Public/somefolder/some.file example: /Public/somefolder/some.file
requestBody:
required: false
content:
multipart/form-data:
schema:
type: object
properties:
filePath:
type: string
post: post:
operationId: SaveFile operationId: SaveFile
responses: responses:

View File

@@ -1,7 +1,14 @@
import path from 'path' import path from 'path'
import { asyncForEach, copy, createFolder, deleteFolder } from '@sasjs/utils' import {
asyncForEach,
copy,
createFile,
createFolder,
deleteFolder,
listFilesInFolder
} from '@sasjs/utils'
import { apiRoot, sasJSCoreMacros } from '../src/utils' import { apiRoot, sasJSCoreMacros, sasJSCoreMacrosInfo } from '../src/utils'
const macroCorePath = path.join(apiRoot, 'node_modules', '@sasjs', 'core') const macroCorePath = path.join(apiRoot, 'node_modules', '@sasjs', 'core')
@@ -16,6 +23,10 @@ export const copySASjsCore = async () => {
await copy(coreSubFolderPath, sasJSCoreMacros) await copy(coreSubFolderPath, sasJSCoreMacros)
}) })
const fileNames = await listFilesInFolder(sasJSCoreMacros)
await createFile(sasJSCoreMacrosInfo, fileNames.join('\n'))
} }
copySASjsCore() copySASjsCore()

View File

@@ -7,9 +7,9 @@ import cors from 'cors'
import { import {
connectDB, connectDB,
copySASjsCore,
getWebBuildFolderPath, getWebBuildFolderPath,
loadAppStreamConfig, loadAppStreamConfig,
sasJSCoreMacros,
setProcessVariables setProcessVariables
} from './utils' } from './utils'
@@ -33,7 +33,7 @@ if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') {
app.use(cookieParser()) app.use(cookieParser())
app.use(morgan('tiny')) app.use(morgan('tiny'))
app.use(express.json({ limit: '50mb' })) app.use(express.json({ limit: '100mb' }))
app.use(express.static(path.join(__dirname, '../public'))) app.use(express.static(path.join(__dirname, '../public')))
const onError: ErrorRequestHandler = (err, req, res, next) => { const onError: ErrorRequestHandler = (err, req, res, next) => {
@@ -42,6 +42,8 @@ const onError: ErrorRequestHandler = (err, req, res, next) => {
} }
export default setProcessVariables().then(async () => { export default setProcessVariables().then(async () => {
await copySASjsCore()
// loading these modules after setting up variables due to // loading these modules after setting up variables due to
// multer's usage of process var process.driveLoc // multer's usage of process var process.driveLoc
const { setupRoutes } = await import('./routes/setupRoutes') const { setupRoutes } = await import('./routes/setupRoutes')
@@ -53,8 +55,6 @@ export default setProcessVariables().then(async () => {
// index.html needs to be injected with some js script. // index.html needs to be injected with some js script.
app.use(express.static(getWebBuildFolderPath())) app.use(express.static(getWebBuildFolderPath()))
console.log('sasJSCoreMacros', sasJSCoreMacros)
app.use(onError) app.use(onError)
await connectDB() await connectDB()

View File

@@ -108,20 +108,14 @@ 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 Delete file from SASjs Drive * @summary Delete file from SASjs Drive
* @query _filePath Location of SAS program * @query _filePath Location of SAS program
* @example _filePath "/Public/somefolder/some.file" * @example _filePath "/Public/somefolder/some.file"
*/ */
@Delete('/file') @Delete('/file')
public async deleteFile( public async deleteFile(@Query() _filePath: string) {
@Query() _filePath?: string, return deleteFile(_filePath)
@FormField() filePath?: string
) {
return deleteFile((_filePath ?? filePath)!)
} }
/** /**
@@ -305,9 +299,3 @@ const updateFile = async (
return { status: 'success' } return { status: 'success' }
} }
const validateFilePath = async (filePath: string) => {
if (!(await fileExists(filePath))) {
throw 'DriveController: File does not exists.'
}
}

View File

@@ -8,14 +8,14 @@ import {
moveFile, moveFile,
readFileBinary readFileBinary
} from '@sasjs/utils' } from '@sasjs/utils'
import { PreProgramVars, TreeNode } from '../../types' import { PreProgramVars, Session, TreeNode } from '../../types'
import { import {
extractHeaders, extractHeaders,
generateFileUploadSasCode, generateFileUploadSasCode,
getTmpFilesFolderPath, getTmpFilesFolderPath,
getTmpMacrosPath,
HTTPHeaders, HTTPHeaders,
isDebugOn, isDebugOn
sasJSCoreMacros
} from '../../utils' } from '../../utils'
export interface ExecutionVars { export interface ExecutionVars {
@@ -39,7 +39,8 @@ export class ExecutionController {
preProgramVariables: PreProgramVars, preProgramVariables: PreProgramVars,
vars: ExecutionVars, vars: ExecutionVars,
otherArgs?: any, otherArgs?: any,
returnJson?: boolean returnJson?: boolean,
session?: Session
) { ) {
if (!(await fileExists(programPath))) if (!(await fileExists(programPath)))
throw 'ExecutionController: SAS file does not exist.' throw 'ExecutionController: SAS file does not exist.'
@@ -51,7 +52,8 @@ export class ExecutionController {
preProgramVariables, preProgramVariables,
vars, vars,
otherArgs, otherArgs,
returnJson returnJson,
session
) )
} }
@@ -60,11 +62,13 @@ export class ExecutionController {
preProgramVariables: PreProgramVars, preProgramVariables: PreProgramVars,
vars: ExecutionVars, vars: ExecutionVars,
otherArgs?: any, otherArgs?: any,
returnJson?: boolean returnJson?: boolean,
sessionByFileUpload?: Session
): Promise<ExecuteReturnRaw | ExecuteReturnJson> { ): Promise<ExecuteReturnRaw | ExecuteReturnJson> {
const sessionController = getSessionController() const sessionController = getSessionController()
const session = await sessionController.getSession() const session =
sessionByFileUpload ?? (await sessionController.getSession())
session.inUse = true session.inUse = true
session.consumed = true session.consumed = true
@@ -106,7 +110,7 @@ export class ExecutionController {
` `
program = ` program = `
options insert=(SASAUTOS="${sasJSCoreMacros}"); options insert=(SASAUTOS="${getTmpMacrosPath()}");
/* runtime vars */ /* runtime vars */
${varStatments} ${varStatments}

View File

@@ -23,7 +23,9 @@ export class FileUploadController {
const sessionController = getSessionController() const sessionController = getSessionController()
session = await sessionController.getSession() 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 req.sasSession = session

View File

@@ -31,7 +31,7 @@ export class SessionController {
? readySessions[0] ? readySessions[0]
: await this.createSession() : await this.createSession()
if (readySessions.length < 2) this.createSession() if (readySessions.length < 3) this.createSession()
return session return session
} }

View File

@@ -1,11 +1,17 @@
import path from 'path' import path from 'path'
import { MemberType, FolderMember, ServiceMember, FileTree } from '../../types' import {
MemberType,
FolderMember,
ServiceMember,
FileTree,
FileMember
} from '../../types'
import { getTmpFilesFolderPath } from '../../utils/file' import { getTmpFilesFolderPath } from '../../utils/file'
import { createFolder, createFile, asyncForEach } from '@sasjs/utils' import { createFolder, createFile, asyncForEach } from '@sasjs/utils'
// REFACTOR: export FileTreeCpntroller // REFACTOR: export FileTreeCpntroller
export const createFileTree = async ( export const createFileTree = async (
members: (FolderMember | ServiceMember)[], members: (FolderMember | ServiceMember | FileMember)[],
parentFolders: string[] = [] parentFolders: string[] = []
) => { ) => {
const destinationPath = path.join( const destinationPath = path.join(
@@ -13,7 +19,9 @@ export const createFileTree = async (
path.join(...parentFolders) path.join(...parentFolders)
) )
await asyncForEach(members, async (member: FolderMember | ServiceMember) => { await asyncForEach(
members,
async (member: FolderMember | ServiceMember | FileMember) => {
let name = member.name let name = member.name
if (member.type === MemberType.service) name += '.sas' if (member.type === MemberType.service) name += '.sas'
@@ -35,7 +43,8 @@ export const createFileTree = async (
encoding encoding
).catch((err) => Promise.reject({ error: err, failedToCreate: name })) ).catch((err) => Promise.reject({ error: err, failedToCreate: name }))
} }
}) }
)
return Promise.resolve() return Promise.resolve()
} }

View File

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

View File

@@ -57,12 +57,11 @@ driveRouter.get('/file', async (req, res) => {
driveRouter.delete('/file', async (req, res) => { driveRouter.delete('/file', async (req, res) => {
const { error: errQ, value: query } = fileParamValidation(req.query) 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 { try {
const response = await controller.deleteFile(query._filePath, body.filePath) const response = await controller.deleteFile(query._filePath)
res.send(response) res.send(response)
} catch (err: any) { } catch (err: any) {
res.status(403).send(err.toString()) res.status(403).send(err.toString())

View File

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

View File

@@ -40,17 +40,6 @@ export const publishAppStream = async (
if (!streamServiceName) { if (!streamServiceName) {
streamServiceName = `AppStreamName${appCount + 1}` 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)) router.use(`/${streamServiceName}`, express.static(pathToDeployment))

View File

@@ -1,23 +1,28 @@
export interface FileTree { export enum MemberType {
members: (FolderMember | ServiceMember)[] service = 'service',
file = 'file',
folder = 'folder'
} }
export enum MemberType { export interface ServiceMember {
folder = 'folder', name: string
service = 'service', type: MemberType.service
file = 'file' code: string
}
export interface FileMember {
name: string
type: MemberType.file
code: string
} }
export interface FolderMember { export interface FolderMember {
name: string name: string
type: MemberType.folder type: MemberType.folder
members: (FolderMember | ServiceMember)[] members: (FolderMember | ServiceMember | FileMember)[]
} }
export interface FileTree {
export interface ServiceMember { members: (FolderMember | ServiceMember | FileMember)[]
name: string
type: MemberType.service | MemberType.file
code: string
} }
export const isFileTree = (arg: any): arg is FileTree => export const isFileTree = (arg: any): arg is FileTree =>
@@ -25,11 +30,25 @@ export const isFileTree = (arg: any): arg is FileTree =>
arg.members && arg.members &&
Array.isArray(arg.members) && Array.isArray(arg.members) &&
arg.members.filter( arg.members.filter(
(member: FolderMember | ServiceMember) => (member: ServiceMember | FileMember | FolderMember) =>
!isFolderMember(member) && !isServiceMember(member) !isServiceMember(member, '-') &&
!isFileMember(member, '-') &&
!isFolderMember(member, '-')
).length === 0 ).length === 0
const isFolderMember = (arg: any): arg is FolderMember => const isServiceMember = (arg: any, pre: string): arg is ServiceMember =>
arg &&
typeof arg.name === 'string' &&
arg.type === MemberType.service &&
typeof arg.code === 'string'
const isFileMember = (arg: any, pre: string): arg is ServiceMember =>
arg &&
typeof arg.name === 'string' &&
arg.type === MemberType.file &&
typeof arg.code === 'string'
const isFolderMember = (arg: any, pre: string): arg is FolderMember =>
arg && arg &&
typeof arg.name === 'string' && typeof arg.name === 'string' &&
arg.type === MemberType.folder && arg.type === MemberType.folder &&
@@ -37,21 +56,7 @@ const isFolderMember = (arg: any): arg is FolderMember =>
Array.isArray(arg.members) && Array.isArray(arg.members) &&
arg.members.filter( arg.members.filter(
(member: FolderMember | ServiceMember) => (member: FolderMember | ServiceMember) =>
!isFolderMember(member) && !isServiceMember(member, pre + '-') &&
!isServiceMember(member) && !isFileMember(member, pre + '-') &&
!isFileMember(member) !isFolderMember(member, pre + '-')
).length === 0 ).length === 0
const isServiceMember = (arg: any): arg is ServiceMember =>
arg &&
typeof arg.name === 'string' &&
arg.type === MemberType.service &&
arg.code &&
typeof arg.code === 'string'
const isFileMember = (arg: any): arg is ServiceMember =>
arg &&
typeof arg.name === 'string' &&
arg.type === MemberType.file &&
arg.code &&
typeof arg.code === 'string'

View File

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

View File

@@ -0,0 +1,34 @@
import path from 'path'
import {
asyncForEach,
createFile,
createFolder,
deleteFolder,
readFile
} from '@sasjs/utils'
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()
await deleteFolder(macrosDrivePath)
await createFolder(macrosDrivePath)
const macros = await readFile(sasJSCoreMacrosInfo)
await asyncForEach(macros.split('\n'), async (macroName) => {
const macroFileSourcePath = path.join(sasJSCoreMacros, macroName)
const macroContent = await readFile(macroFileSourcePath)
const macroFileDestPath = path.join(macrosDrivePath, macroName)
await createFile(macroFileDestPath, macroContent)
})
console.log('Macros Drive Path:', macrosDrivePath)
}

View File

@@ -9,6 +9,7 @@ export const sysInitCompiledPath = path.join(
) )
export const sasJSCoreMacros = path.join(apiRoot, 'sasjscore') export const sasJSCoreMacros = path.join(apiRoot, 'sasjscore')
export const sasJSCoreMacrosInfo = path.join(apiRoot, 'sasjscore', '.macrolist')
export const getWebBuildFolderPath = () => export const getWebBuildFolderPath = () =>
path.join(codebaseRoot, 'web', 'build') path.join(codebaseRoot, 'web', 'build')
@@ -18,6 +19,8 @@ export const getTmpFolderPath = () => process.driveLoc
export const getTmpAppStreamConfigPath = () => export const getTmpAppStreamConfigPath = () =>
path.join(getTmpFolderPath(), 'appStreamConfig.json') path.join(getTmpFolderPath(), 'appStreamConfig.json')
export const getTmpMacrosPath = () => path.join(getTmpFolderPath(), 'sasjscore')
export const getTmpUploadsPath = () => path.join(getTmpFolderPath(), 'uploads') export const getTmpUploadsPath = () => path.join(getTmpFolderPath(), 'uploads')
export const getTmpFilesFolderPath = () => export const getTmpFilesFolderPath = () =>

View File

@@ -1,13 +1,14 @@
export * from './appStreamConfig' export * from './appStreamConfig'
export * from './connectDB' export * from './connectDB'
export * from './copySASjsCore'
export * from './extractHeaders' export * from './extractHeaders'
export * from './file' export * from './file'
export * from './generateAccessToken' export * from './generateAccessToken'
export * from './generateAuthCode' export * from './generateAuthCode'
export * from './generateRefreshToken' export * from './generateRefreshToken'
export * from './isDebugOn'
export * from './getCertificates' export * from './getCertificates'
export * from './getDesktopFields' export * from './getDesktopFields'
export * from './isDebugOn'
export * from './parseLogToArray' export * from './parseLogToArray'
export * from './removeTokensInDB' export * from './removeTokensInDB'
export * from './saveTokensInDB' export * from './saveTokensInDB'

View File

@@ -1,9 +1,21 @@
import path from 'path'
import fs from 'fs'
import { getTmpSessionsFolderPath } from '.'
import { MulterFile } from '../types/Upload' import { MulterFile } from '../types/Upload'
import { listFilesInFolder } from '@sasjs/utils' import { listFilesInFolder } from '@sasjs/utils'
interface FilenameMapSingle {
fieldName: string
originalName: string
}
interface FilenamesMap {
[key: string]: FilenameMapSingle
}
interface UploadedFiles extends FilenameMapSingle {
fileref: string
filepath: string
count: number
}
/** /**
* It will create an object that maps hashed file names to the original names * It will create an object that maps hashed file names to the original names
* @param files array of files to be mapped * @param files array of files to be mapped
@@ -12,10 +24,13 @@ import { listFilesInFolder } from '@sasjs/utils'
export const makeFilesNamesMap = (files: MulterFile[]) => { export const makeFilesNamesMap = (files: MulterFile[]) => {
if (!files) return null if (!files) return null
const filesNamesMap: { [key: string]: string } = {} const filesNamesMap: FilenamesMap = {}
for (let file of files) { for (let file of files) {
filesNamesMap[file.filename] = file.originalname filesNamesMap[file.filename] = {
fieldName: file.fieldname,
originalName: file.originalname
}
} }
return filesNamesMap return filesNamesMap
@@ -28,17 +43,12 @@ export const makeFilesNamesMap = (files: MulterFile[]) => {
* @returns generated sas code * @returns generated sas code
*/ */
export const generateFileUploadSasCode = async ( export const generateFileUploadSasCode = async (
filesNamesMap: any, filesNamesMap: FilenamesMap,
sasSessionFolder: string sasSessionFolder: string
): Promise<string> => { ): Promise<string> => {
let uploadSasCode = '' let uploadSasCode = ''
let fileCount = 0 let fileCount = 0
let uploadedFilesMap: { const uploadedFiles: UploadedFiles[] = []
fileref: string
filepath: string
filename: string
count: number
}[] = []
const sasSessionFolderList: string[] = await listFilesInFolder( const sasSessionFolderList: string[] = await listFilesInFolder(
sasSessionFolder sasSessionFolder
@@ -50,31 +60,32 @@ export const generateFileUploadSasCode = async (
if (fileName.includes('req_file')) { if (fileName.includes('req_file')) {
fileCount++ fileCount++
uploadedFilesMap.push({ uploadedFiles.push({
fileref: `_sjs${fileCountString}`, fileref: `_sjs${fileCountString}`,
filepath: `${sasSessionFolder}/${fileName}`, filepath: `${sasSessionFolder}/${fileName}`,
filename: filesNamesMap[fileName], originalName: filesNamesMap[fileName].originalName,
fieldName: filesNamesMap[fileName].fieldName,
count: fileCount count: fileCount
}) })
} }
}) })
for (let uploadedMap of uploadedFilesMap) { for (const uploadedFile of uploadedFiles) {
uploadSasCode += `\nfilename ${uploadedMap.fileref} "${uploadedMap.filepath}";` uploadSasCode += `\nfilename ${uploadedFile.fileref} "${uploadedFile.filepath}";`
} }
uploadSasCode += `\n%let _WEBIN_FILE_COUNT=${fileCount};` uploadSasCode += `\n%let _WEBIN_FILE_COUNT=${fileCount};`
for (let uploadedMap of uploadedFilesMap) { for (const uploadedFile of uploadedFiles) {
uploadSasCode += `\n%let _WEBIN_FILENAME${uploadedMap.count}=${uploadedMap.filename};` uploadSasCode += `\n%let _WEBIN_FILENAME${uploadedFile.count}=${uploadedFile.originalName};`
} }
for (let uploadedMap of uploadedFilesMap) { for (const uploadedFile of uploadedFiles) {
uploadSasCode += `\n%let _WEBIN_FILEREF${uploadedMap.count}=${uploadedMap.fileref};` uploadSasCode += `\n%let _WEBIN_FILEREF${uploadedFile.count}=${uploadedFile.fileref};`
} }
for (let uploadedMap of uploadedFilesMap) { for (const uploadedFile of uploadedFiles) {
uploadSasCode += `\n%let _WEBIN_NAME${uploadedMap.count}=${uploadedMap.filepath};` uploadSasCode += `\n%let _WEBIN_NAME${uploadedFile.count}=${uploadedFile.fieldName};`
} }
if (fileCount > 0) { if (fileCount > 0) {

4
package-lock.json generated
View File

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

View File

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

View File

@@ -1,16 +1,14 @@
### testing upload file example ### testing upload file example
POST http://localhost:5000/SASjsApi/stp/execute/?_program=/Public/app/viya/services/editors/loadfile&table=DCCONFIG.MPE_X_TEST POST http://localhost:5000/SASjsApi/stp/execute/?_program=/Public/app/viya/services/editors/loadfile&table=DCCONFIG.MPE_X_TEST
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarynkYOqevUMKZrXeAy Content-Type: multipart/form-data; boundary=----WebKitFormBoundarynkYOqevUMKZrXeAy
------WebKitFormBoundarynkYOqevUMKZrXeAy ------WebKitFormBoundarynkYOqevUMKZrXeAy
Content-Disposition: form-data; name="file"; filename="DCCONFIG.MPE_X_TEST.xlsx" Content-Disposition: form-data; name="fileSome11"; filename="DCCONFIG.MPE_X_TEST.xlsx"
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
------WebKitFormBoundarynkYOqevUMKZrXeAy ------WebKitFormBoundarynkYOqevUMKZrXeAy
Content-Disposition: form-data; name="file"; filename="DCCONFIG.MPE_X_TEST.xlsx.csv" Content-Disposition: form-data; name="fileSome22"; filename="DCCONFIG.MPE_X_TEST.xlsx.csv"
Content-Type: application/csv Content-Type: application/csv
_____DELETE__THIS__RECORD_____,PRIMARY_KEY_FIELD,SOME_CHAR,SOME_DROPDOWN,SOME_NUM,SOME_DATE,SOME_DATETIME,SOME_TIME,SOME_SHORTNUM,SOME_BESTNUM _____DELETE__THIS__RECORD_____,PRIMARY_KEY_FIELD,SOME_CHAR,SOME_DROPDOWN,SOME_NUM,SOME_DATE,SOME_DATETIME,SOME_TIME,SOME_SHORTNUM,SOME_BESTNUM

View File

@@ -54,11 +54,11 @@ const Studio = () => {
let weboutString: string let weboutString: string
try { try {
weboutString = res.data.webout weboutString = res.data._webout
.split('>>weboutBEGIN<<')[1] .split('>>weboutBEGIN<<')[1]
.split('>>weboutEND<<')[0] .split('>>weboutEND<<')[0]
} catch (_) { } catch (_) {
weboutString = res?.data?.webout ?? '' weboutString = res?.data?._webout ?? ''
} }
let webout: string let webout: string
@@ -70,6 +70,9 @@ const Studio = () => {
setWebout(`<pre><code>${webout}</code></pre>`) setWebout(`<pre><code>${webout}</code></pre>`)
setTab('2') setTab('2')
// Scroll to bottom of log
window.scrollTo(0, document.body.scrollHeight)
}) })
.catch((err) => console.log(err)) .catch((err) => console.log(err))
} }