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

Compare commits

...

26 Commits

Author SHA1 Message Date
semantic-release-bot
5d576aff91 chore(release): 0.37.0 [skip ci]
# [0.37.0](https://github.com/sasjs/server/compare/v0.36.0...v0.37.0) (2024-10-29)

### Features

* **stp:** added trigger endpoint ([b0723f1](b0723f1444))
2024-10-29 14:11:35 +00:00
Yury Shkoda
a044176054 Merge pull request #375 from sasjs/issue-373-stp
Issue 373 stp
2024-10-29 17:08:38 +03:00
Yury
deee34f5fd chore(stp): removed query logic from trigger endpoint 2024-10-29 16:55:40 +03:00
Yury
b0723f1444 feat(stp): added trigger endpoint 2024-10-29 16:27:53 +03:00
Yury
e9519cb3c6 chore(code): used correct type 2024-10-29 16:20:27 +03:00
semantic-release-bot
0838b8112e chore(release): 0.36.0 [skip ci]
# [0.36.0](https://github.com/sasjs/server/compare/v0.35.4...v0.36.0) (2024-10-29)

### Features

* **code:** added code/trigger API endpoint ([ffcf193](ffcf193b87))
2024-10-29 10:32:01 +00:00
Yury Shkoda
441f8b7726 Merge pull request #374 from sasjs/issue-373
feat(code): added code/trigger API endpoint
2024-10-29 13:29:08 +03:00
Yury
049a7f4b80 chore(swagger): improved description 2024-10-29 12:02:26 +03:00
Yury
3053c68bdf chore(lint): fixed linting issues 2024-10-29 11:40:44 +03:00
Yury
76750e864d chore(lint): fixed lint issue 2024-10-29 11:30:05 +03:00
Yury
ffcf193b87 feat(code): added code/trigger API endpoint 2024-10-29 11:18:04 +03:00
semantic-release-bot
aa2a1cbe13 chore(release): 0.35.4 [skip ci]
## [0.35.4](https://github.com/sasjs/server/compare/v0.35.3...v0.35.4) (2024-01-15)

### Bug Fixes

* **api:** fixed env issue in MacOS executable ([73d965d](73d965daf5))
2024-01-15 13:21:15 +00:00
Yury Shkoda
6f2c53555c Merge pull request #372 from sasjs/issue-371
fix(api): fixed env issue in MacOS executable
2024-01-15 16:18:10 +03:00
Yury
73d965daf5 fix(api): fixed env issue in MacOS executable 2024-01-15 15:14:06 +03:00
semantic-release-bot
4f1763db67 chore(release): 0.35.3 [skip ci]
## [0.35.3](https://github.com/sasjs/server/compare/v0.35.2...v0.35.3) (2023-11-07)

### Bug Fixes

* enable embedded LFs in JS STP vars ([7e8cbbf](7e8cbbf377))
2023-11-07 20:48:28 +00:00
Allan Bowe
28222add04 Merge pull request #370 from sasjs/allanbowe-patch-1
fix: enable embedded LFs in JS STP vars
2023-11-07 20:43:16 +00:00
Allan
068edfd6a5 chore: lint fix 2023-11-07 20:39:05 +00:00
Allan Bowe
7e8cbbf377 fix: enable embedded LFs in JS STP vars 2023-11-07 15:51:32 +00:00
Allan Bowe
1fc1431442 chore: using GITHUB_TOKEN 2023-08-07 20:11:40 +01:00
semantic-release-bot
3387efbb9a chore(release): 0.35.2 [skip ci]
## [0.35.2](https://github.com/sasjs/server/compare/v0.35.1...v0.35.2) (2023-08-07)

### Bug Fixes

* add _debug as optional query param in swagger apis for GET stp/execute ([9586dbb](9586dbb2d0))
2023-08-07 18:53:12 +00:00
Allan Bowe
e2996b495f Merge pull request #365 from sasjs/swagger-fix
fix: add _debug as optional query param in swagger apis for  stp/execute
2023-08-07 19:48:28 +01:00
Allan
41c627f93a chore: lint fix 2023-08-07 19:39:02 +01:00
Allan Bowe
49f5dc7555 Update swagger.yaml 2023-08-07 19:32:29 +01:00
Allan Bowe
f6e77f99a4 Update swagger.yaml 2023-08-07 19:31:20 +01:00
Allan Bowe
b57dfa429b Update stp.ts 2023-08-07 19:30:09 +01:00
9586dbb2d0 fix: add _debug as optional query param in swagger apis for GET stp/execute 2023-08-07 22:01:52 +05:00
17 changed files with 488 additions and 42 deletions

View File

@@ -56,4 +56,4 @@ jobs:
- name: Release
run: |
GITHUB_TOKEN=${{ secrets.GH_TOKEN }} semantic-release
GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} semantic-release

View File

@@ -1,3 +1,38 @@
# [0.37.0](https://github.com/sasjs/server/compare/v0.36.0...v0.37.0) (2024-10-29)
### Features
* **stp:** added trigger endpoint ([b0723f1](https://github.com/sasjs/server/commit/b0723f14448d60ffce4f2175cf8a73fc4d4dd0ee))
# [0.36.0](https://github.com/sasjs/server/compare/v0.35.4...v0.36.0) (2024-10-29)
### Features
* **code:** added code/trigger API endpoint ([ffcf193](https://github.com/sasjs/server/commit/ffcf193b87d811b166d79af74013776a253b50b0))
## [0.35.4](https://github.com/sasjs/server/compare/v0.35.3...v0.35.4) (2024-01-15)
### Bug Fixes
* **api:** fixed env issue in MacOS executable ([73d965d](https://github.com/sasjs/server/commit/73d965daf54b16c0921e4b18d11a1e6f8650884d))
## [0.35.3](https://github.com/sasjs/server/compare/v0.35.2...v0.35.3) (2023-11-07)
### Bug Fixes
* enable embedded LFs in JS STP vars ([7e8cbbf](https://github.com/sasjs/server/commit/7e8cbbf377b27a7f5dd9af0bc6605c01f302f5d9))
## [0.35.2](https://github.com/sasjs/server/compare/v0.35.1...v0.35.2) (2023-08-07)
### Bug Fixes
* add _debug as optional query param in swagger apis for GET stp/execute ([9586dbb](https://github.com/sasjs/server/commit/9586dbb2d0d6611061c9efdfb84030144f62c2ee))
## [0.35.1](https://github.com/sasjs/server/compare/v0.35.0...v0.35.1) (2023-07-25)

View File

@@ -25,7 +25,7 @@ LDAP_USERS_BASE_DN = <ou=users,dc=cloudron>
LDAP_GROUPS_BASE_DN = <ou=groups,dc=cloudron>
#default value is 100
MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY=100
MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY=100
#default value is 10
MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP=10

16
api/package-lock.json generated
View File

@@ -51,7 +51,7 @@
"adm-zip": "^0.5.9",
"axios": "0.27.2",
"csrf": "^3.1.0",
"dotenv": "^10.0.0",
"dotenv": "^16.0.1",
"http-headers-validation": "^0.0.1",
"jest": "^27.0.6",
"mongodb-memory-server": "8.11.4",
@@ -4788,12 +4788,12 @@
}
},
"node_modules/dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
"integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==",
"dev": true,
"engines": {
"node": ">=10"
"node": ">=12"
}
},
"node_modules/duplexer2": {
@@ -15217,9 +15217,9 @@
}
},
"dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
"integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==",
"dev": true
},
"duplexer2": {

View File

@@ -88,7 +88,7 @@
"adm-zip": "^0.5.9",
"axios": "0.27.2",
"csrf": "^3.1.0",
"dotenv": "^10.0.0",
"dotenv": "^16.0.1",
"http-headers-validation": "^0.0.1",
"jest": "^27.0.6",
"mongodb-memory-server": "8.11.4",

View File

@@ -98,17 +98,47 @@ components:
properties:
code:
type: string
description: 'Code of program'
example: '* Code HERE;'
description: 'The code to be executed'
example: '* Your Code HERE;'
runTime:
$ref: '#/components/schemas/RunTimeType'
description: 'runtime for program'
description: 'The runtime for the code - eg SAS, JS, PY or R'
example: js
required:
- code
- runTime
type: object
additionalProperties: false
TriggerCodeResponse:
properties:
sessionId:
type: string
description: "The SessionId is the name of the temporary folder used to store the outputs.\nFor SAS, this would be the SASWORK folder. Can be used to poll job status.\nThis session ID should be used to poll job status."
example: '{ sessionId: ''20241028074744-54132-1730101664824'' }'
required:
- sessionId
type: object
additionalProperties: false
TriggerCodePayload:
properties:
code:
type: string
description: 'The code to be executed'
example: '* Your Code HERE;'
runTime:
$ref: '#/components/schemas/RunTimeType'
description: 'The runtime for the code - eg SAS, JS, PY or R'
example: sas
expiresAfterMins:
type: number
format: double
description: "Amount of minutes after the completion of the job when the session must be\ndestroyed."
example: 15
required:
- code
- runTime
type: object
additionalProperties: false
MemberType.folder:
enum:
- folder
@@ -563,6 +593,31 @@ components:
example: /Public/somefolder/some.file
type: object
additionalProperties: false
TriggerProgramResponse:
properties:
sessionId:
type: string
description: "The SessionId is the name of the temporary folder used to store the outputs.\nFor SAS, this would be the SASWORK folder. Can be used to poll program status.\nThis session ID should be used to poll program status."
example: '{ sessionId: ''20241028074744-54132-1730101664824'' }'
required:
- sessionId
type: object
additionalProperties: false
TriggerProgramPayload:
properties:
_program:
type: string
description: 'Location of SAS program'
example: /Public/somefolder/some.file
expiresAfterMins:
type: number
format: double
description: "Amount of minutes after the completion of the program when the session must be\ndestroyed."
example: 15
required:
- _program
type: object
additionalProperties: false
LoginPayload:
properties:
username:
@@ -792,7 +847,7 @@ paths:
- {type: string}
- {type: string, format: byte}
description: 'Execute Code on the Specified Runtime'
summary: 'Run Code and Return Webout Content and Log'
summary: "Run Code and Return Webout Content, Log and Print output\nThe order of returned parts of the payload is:\n1. Webout (if present)\n2. Logs UUID (used as separator)\n3. Log\n4. Logs UUID (used as separator)\n5. Print (if present and if the runtime is SAS)\nPlease see"
tags:
- Code
security:
@@ -805,6 +860,30 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ExecuteCodePayload'
/SASjsApi/code/trigger:
post:
operationId: TriggerCode
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: '#/components/schemas/TriggerCodeResponse'
description: 'Trigger Code on the Specified Runtime'
summary: 'Triggers code and returns SessionId immediately - does not wait for job completion'
tags:
- Code
security:
-
bearerAuth: []
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TriggerCodePayload'
/SASjsApi/drive/deploy:
post:
operationId: Deploy
@@ -1789,7 +1868,7 @@ paths:
anyOf:
- {type: string}
- {type: string, format: byte}
description: "Trigger a Stored Program using the _program URL parameter.\n\nAccepts URL parameters and file uploads. For more details, see docs:\n\nhttps://server.sasjs.io/storedprograms"
description: "Trigger a Stored Program using the _program URL parameter.\n\nAccepts additional URL parameters (converted to session variables)\nand file uploads. For more details, see docs:\n\nhttps://server.sasjs.io/storedprograms"
summary: 'Execute a Stored Program, returns _webout and (optionally) log.'
tags:
- STP
@@ -1798,13 +1877,22 @@ paths:
bearerAuth: []
parameters:
-
description: 'Location of code in SASjs Drive'
description: 'Location of Stored Program in SASjs Drive.'
in: query
name: _program
required: true
schema:
type: string
example: /Projects/myApp/some/program
-
description: 'Optional query param for setting debug mode (returns the session log in the response body).'
in: query
name: _debug
required: false
schema:
format: double
type: number
example: 131
post:
operationId: ExecutePostRequest
responses:
@@ -1838,6 +1926,38 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ExecutePostRequestPayload'
/SASjsApi/stp/trigger:
post:
operationId: TriggerProgram
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: '#/components/schemas/TriggerProgramResponse'
description: 'Trigger Program on the Specified Runtime'
summary: 'Triggers program and returns SessionId immediately - does not wait for program completion'
tags:
- STP
security:
-
bearerAuth: []
parameters:
-
description: 'Location of code in SASjs Drive'
in: query
name: _program
required: false
schema:
type: string
example: /Projects/myApp/some/program
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TriggerProgramPayload'
/:
get:
operationId: Home

View File

@@ -1,27 +1,55 @@
import express from 'express'
import { Request, Security, Route, Tags, Post, Body } from 'tsoa'
import { ExecutionController } from './internal'
import { ExecutionController, getSessionController } from './internal'
import {
getPreProgramVariables,
getUserAutoExec,
ModeType,
parseLogToArray,
RunTimeType
} from '../utils'
interface ExecuteCodePayload {
/**
* Code of program
* @example "* Code HERE;"
* The code to be executed
* @example "* Your Code HERE;"
*/
code: string
/**
* runtime for program
* The runtime for the code - eg SAS, JS, PY or R
* @example "js"
*/
runTime: RunTimeType
}
interface TriggerCodePayload {
/**
* The code to be executed
* @example "* Your Code HERE;"
*/
code: string
/**
* The runtime for the code - eg SAS, JS, PY or R
* @example "sas"
*/
runTime: RunTimeType
/**
* Amount of minutes after the completion of the job when the session must be
* destroyed.
* @example 15
*/
expiresAfterMins?: number
}
interface TriggerCodeResponse {
/**
* The SessionId is the name of the temporary folder used to store the outputs.
* For SAS, this would be the SASWORK folder. Can be used to poll job status.
* This session ID should be used to poll job status.
* @example "{ sessionId: '20241028074744-54132-1730101664824' }"
*/
sessionId: string
}
@Security('bearerAuth')
@Route('SASjsApi/code')
@Tags('Code')
@@ -44,6 +72,18 @@ export class CodeController {
): Promise<string | Buffer> {
return executeCode(request, body)
}
/**
* Trigger Code on the Specified Runtime
* @summary Triggers code and returns SessionId immediately - does not wait for job completion
*/
@Post('/trigger')
public async triggerCode(
@Request() request: express.Request,
@Body() body: TriggerCodePayload
): Promise<TriggerCodeResponse> {
return triggerCode(request, body)
}
}
const executeCode = async (
@@ -76,3 +116,49 @@ const executeCode = async (
}
}
}
const triggerCode = async (
req: express.Request,
{ code, runTime, expiresAfterMins }: TriggerCodePayload
): Promise<TriggerCodeResponse> => {
const { user } = req
const userAutoExec =
process.env.MODE === ModeType.Server
? user?.autoExec
: await getUserAutoExec()
// get session controller based on runTime
const sessionController = getSessionController(runTime)
// get session
const session = await sessionController.getSession()
// add expiresAfterMins to session if provided
if (expiresAfterMins) {
// expiresAfterMins.used is set initially to false
session.expiresAfterMins = { mins: expiresAfterMins, used: false }
}
try {
// call executeProgram method of ExecutionController without awaiting
new ExecutionController().executeProgram({
program: code,
preProgramVariables: getPreProgramVariables(req),
vars: { ...req.query, _debug: 131 },
otherArgs: { userAutoExec },
runTime: runTime,
includePrintOutput: true,
session // session is provided
})
// return session id
return { sessionId: session.id }
} catch (err: any) {
throw {
code: 400,
status: 'failure',
message: 'Job execution failed.',
error: typeof err === 'object' ? err.toString() : err
}
}
}

View File

@@ -14,8 +14,7 @@ import {
createFile,
fileExists,
generateTimestamp,
readFile,
isWindows
readFile
} from '@sasjs/utils'
const execFilePromise = promisify(execFile)
@@ -194,12 +193,29 @@ ${autoExecContent}`
async () => {
if (session.inUse) {
// adding 10 more minutes
const newDeathTimeStamp = parseInt(session.deathTimeStamp) + 10 * 1000
const newDeathTimeStamp =
parseInt(session.deathTimeStamp) + 10 * 60 * 1000
session.deathTimeStamp = newDeathTimeStamp.toString()
this.scheduleSessionDestroy(session)
} else {
await this.deleteSession(session)
const { expiresAfterMins } = session
// delay session destroy if expiresAfterMins present
if (expiresAfterMins && !expiresAfterMins.used) {
// calculate session death time using expiresAfterMins
const newDeathTimeStamp =
parseInt(session.deathTimeStamp) +
expiresAfterMins.mins * 60 * 1000
session.deathTimeStamp = newDeathTimeStamp.toString()
// set expiresAfterMins to true to avoid using it again
session.expiresAfterMins!.used = true
this.scheduleSessionDestroy(session)
} else {
await this.deleteSession(session)
}
}
},
parseInt(session.deathTimeStamp) - new Date().getTime() - 100

View File

@@ -15,7 +15,7 @@ export const createJSProgram = async (
) => {
const varStatments = Object.keys(vars).reduce(
(computed: string, key: string) =>
`${computed}const ${key} = '${vars[key]}';\n`,
`${computed}const ${key} = \`${vars[key]}\`;\n`,
''
)

View File

@@ -1,6 +1,10 @@
import express from 'express'
import { Request, Security, Route, Tags, Post, Body, Get, Query } from 'tsoa'
import { ExecutionController, ExecutionVars } from './internal'
import {
ExecutionController,
ExecutionVars,
getSessionController
} from './internal'
import {
getPreProgramVariables,
makeFilesNamesMap,
@@ -16,6 +20,30 @@ interface ExecutePostRequestPayload {
_program?: string
}
interface TriggerProgramPayload {
/**
* Location of SAS program
* @example "/Public/somefolder/some.file"
*/
_program: string
/**
* Amount of minutes after the completion of the program when the session must be
* destroyed.
* @example 15
*/
expiresAfterMins?: number
}
interface TriggerProgramResponse {
/**
* The SessionId is the name of the temporary folder used to store the outputs.
* For SAS, this would be the SASWORK folder. Can be used to poll program status.
* This session ID should be used to poll program status.
* @example "{ sessionId: '20241028074744-54132-1730101664824' }"
*/
sessionId: string
}
@Security('bearerAuth')
@Route('SASjsApi/stp')
@Tags('STP')
@@ -23,20 +51,30 @@ export class STPController {
/**
* Trigger a Stored Program using the _program URL parameter.
*
* Accepts URL parameters and file uploads. For more details, see docs:
* Accepts additional URL parameters (converted to session variables)
* and file uploads. For more details, see docs:
*
* https://server.sasjs.io/storedprograms
*
* @summary Execute a Stored Program, returns _webout and (optionally) log.
* @param _program Location of code in SASjs Drive
* @param _program Location of Stored Program in SASjs Drive.
* @param _debug Optional query param for setting debug mode (returns the session log in the response body).
* @example _program "/Projects/myApp/some/program"
* @example _debug 131
*/
@Get('/execute')
public async executeGetRequest(
@Request() request: express.Request,
@Query() _program: string
@Query() _program: string,
@Query() _debug?: number
): Promise<string | Buffer> {
const vars = request.query as ExecutionVars
let vars = request.query as ExecutionVars
if (_debug) {
vars = {
...vars,
_debug
}
}
return execute(request, _program, vars)
}
@@ -68,6 +106,22 @@ export class STPController {
return execute(request, program!, vars, otherArgs)
}
/**
* Trigger Program on the Specified Runtime
* @summary Triggers program and returns SessionId immediately - does not wait for program completion
* @param _program Location of code in SASjs Drive
* @example _program "/Projects/myApp/some/program"
* @param expiresAfterMins Amount of minutes after the completion of the program when the session must be destroyed
* @example expiresAfterMins 15
*/
@Post('/trigger')
public async triggerProgram(
@Request() request: express.Request,
@Body() body: TriggerProgramPayload
): Promise<TriggerProgramResponse> {
return triggerProgram(request, body)
}
}
const execute = async (
@@ -106,3 +160,49 @@ const execute = async (
}
}
}
const triggerProgram = async (
req: express.Request,
{ _program, expiresAfterMins }: TriggerProgramPayload
): Promise<TriggerProgramResponse> => {
try {
const vars = { ...req.body }
const filesNamesMap = req.files?.length
? makeFilesNamesMap(req.files as MulterFile[])
: null
const otherArgs = { filesNamesMap: filesNamesMap }
const { codePath, runTime } = await getRunTimeAndFilePath(_program)
// get session controller based on runTime
const sessionController = getSessionController(runTime)
// get session
const session = await sessionController.getSession()
// add expiresAfterMins to session if provided
if (expiresAfterMins) {
// expiresAfterMins.used is set initially to false
session.expiresAfterMins = { mins: expiresAfterMins, used: false }
}
// call executeFile method of ExecutionController without awaiting
new ExecutionController().executeFile({
programPath: codePath,
runTime,
preProgramVariables: getPreProgramVariables(req),
vars,
otherArgs,
session
})
// return session id
return { sessionId: session.id }
} catch (err: any) {
throw {
code: 400,
status: 'failure',
message: 'Job execution failed.',
error: typeof err === 'object' ? err.toString() : err
}
}
}

View File

@@ -285,7 +285,7 @@ const getUser = async (
username: user.username,
isActive: user.isActive,
isAdmin: user.isAdmin,
autoExec: getAutoExec ? user.autoExec ?? '' : undefined,
autoExec: getAutoExec ? (user.autoExec ?? '') : undefined,
groups: user.groups
}
}

View File

@@ -1,5 +1,5 @@
import express from 'express'
import { runCodeValidation } from '../../utils'
import { runCodeValidation, triggerCodeValidation } from '../../utils'
import { CodeController } from '../../controllers/'
const runRouter = express.Router()
@@ -28,4 +28,22 @@ runRouter.post('/execute', async (req, res) => {
}
})
runRouter.post('/trigger', async (req, res) => {
const { error, value: body } = triggerCodeValidation(req.body)
if (error) return res.status(400).send(error.details[0].message)
try {
const response = await controller.triggerCode(req, body)
res.status(200)
res.send(response)
} catch (err: any) {
const statusCode = err.code
delete err.code
res.status(statusCode).send(err)
}
})
export default runRouter

View File

@@ -1,5 +1,8 @@
import express from 'express'
import { executeProgramRawValidation } from '../../utils'
import {
executeProgramRawValidation,
triggerProgramValidation
} from '../../utils'
import { STPController } from '../../controllers/'
import { FileUploadController } from '../../controllers/internal'
@@ -13,7 +16,11 @@ stpRouter.get('/execute', async (req, res) => {
if (error) return res.status(400).send(error.details[0].message)
try {
const response = await controller.executeGetRequest(req, query._program)
const response = await controller.executeGetRequest(
req,
query._program,
query._debug
)
if (response instanceof Buffer) {
res.writeHead(200, (req as any).sasHeaders)
@@ -65,4 +72,23 @@ stpRouter.post(
}
)
stpRouter.post('/trigger', async (req, res) => {
const { error, value: body } = triggerProgramValidation(req.body)
if (error) return res.status(400).send(error.details[0].message)
try {
const response = await controller.triggerProgram(req, body)
res.status(200)
res.send(response)
} catch (err: any) {
const statusCode = err.code
delete err.code
res.status(statusCode).send(err)
}
})
export default stpRouter

View File

@@ -8,4 +8,5 @@ export interface Session {
consumed: boolean
completed: boolean
crashed?: string
expiresAfterMins?: { mins: number; used: boolean }
}

View File

@@ -1,9 +1,31 @@
import path from 'path'
import { createFolder, getAbsolutePath, getRealPath } from '@sasjs/utils'
import {
createFolder,
getAbsolutePath,
getRealPath,
fileExists
} from '@sasjs/utils'
import dotenv from 'dotenv'
import { connectDB, getDesktopFields, ModeType, RunTimeType, SECRETS } from '.'
export const setProcessVariables = async () => {
const { execPath } = process
// Check if execPath ends with 'api-macos' to determine executable for MacOS.
// This is needed to fix picking .env file issue in MacOS executable.
if (execPath) {
const envPathSplitted = execPath.split(path.sep)
if (envPathSplitted.pop() === 'api-macos') {
const envPath = path.join(envPathSplitted.join(path.sep), '.env')
// Override environment variables from envPath if file exists
if (await fileExists(envPath)) {
dotenv.config({ path: envPath, override: true })
}
}
}
const { MODE, RUN_TIMES } = process.env
if (MODE === ModeType.Server) {
@@ -21,6 +43,7 @@ export const setProcessVariables = async () => {
if (process.env.NODE_ENV === 'test') {
process.sasjsRoot = path.join(process.cwd(), 'sasjs_root')
process.driveLoc = path.join(process.cwd(), 'sasjs_root', 'drive')
return
}
@@ -41,7 +64,9 @@ export const setProcessVariables = async () => {
const { SASJS_ROOT } = process.env
const absPath = getAbsolutePath(SASJS_ROOT ?? 'sasjs_root', process.cwd())
await createFolder(absPath)
process.sasjsRoot = getRealPath(absPath)
const { DRIVE_LOCATION } = process.env
@@ -49,6 +74,7 @@ export const setProcessVariables = async () => {
DRIVE_LOCATION ?? path.join(process.sasjsRoot, 'drive'),
process.cwd()
)
await createFolder(absDrivePath)
process.driveLoc = getRealPath(absDrivePath)
@@ -57,7 +83,9 @@ export const setProcessVariables = async () => {
LOG_LOCATION ?? path.join(process.sasjsRoot, 'logs'),
process.cwd()
)
await createFolder(absLogsPath)
process.logsLoc = getRealPath(absLogsPath)
process.logsUUID = 'SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784'

View File

@@ -51,9 +51,8 @@ export const generateFileUploadSasCode = async (
let fileCount = 0
const uploadedFiles: UploadedFiles[] = []
const sasSessionFolderList: string[] = await listFilesInFolder(
sasSessionFolder
)
const sasSessionFolderList: string[] =
await listFilesInFolder(sasSessionFolder)
sasSessionFolderList.forEach((fileName) => {
let fileCountString = fileCount < 100 ? '0' + fileCount : fileCount
fileCountString = fileCount < 10 ? '00' + fileCount : fileCount

View File

@@ -178,9 +178,26 @@ export const runCodeValidation = (data: any): Joi.ValidationResult =>
runTime: Joi.string().valid(...process.runTimes)
}).validate(data)
export const triggerCodeValidation = (data: any): Joi.ValidationResult =>
Joi.object({
code: Joi.string().required(),
runTime: Joi.string().valid(...process.runTimes),
expiresAfterMins: Joi.number().greater(0)
}).validate(data)
export const executeProgramRawValidation = (data: any): Joi.ValidationResult =>
Joi.object({
_program: Joi.string().required()
_program: Joi.string().required(),
_debug: Joi.number()
})
.pattern(/^/, Joi.alternatives(Joi.string(), Joi.number()))
.validate(data)
export const triggerProgramValidation = (data: any): Joi.ValidationResult =>
Joi.object({
_program: Joi.string().required(),
_debug: Joi.number(),
expiresAfterMins: Joi.number().greater(0)
})
.pattern(/^/, Joi.alternatives(Joi.string(), Joi.number()))
.validate(data)