mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 19:34:34 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c261745f1d | ||
|
|
d6e527ecf2 | ||
|
|
bc2cff1d0d | ||
|
|
66aa9b5891 | ||
|
|
ca17e7c192 | ||
|
|
73df102422 | ||
|
|
48a9a4dd0e | ||
|
|
4f6f735f5b | ||
|
|
6b6546c7ad | ||
|
|
f94ddc0352 | ||
|
|
03670cf0d6 |
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -1,5 +1,3 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": ["autoexec", "initialising"]
|
||||||
"autoexec"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,3 +1,15 @@
|
|||||||
|
# [0.39.0](https://github.com/sasjs/server/compare/v0.38.0...v0.39.0) (2024-10-31)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api:** fixed condition in processProgram ([48a9a4d](https://github.com/sasjs/server/commit/48a9a4dd0e31f84209635382be4ec4bb2c3a9c0c))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **api:** added session state endpoint ([6b6546c](https://github.com/sasjs/server/commit/6b6546c7ad0833347f8dc4cdba6ad19132f7aaef))
|
||||||
|
|
||||||
# [0.38.0](https://github.com/sasjs/server/compare/v0.37.0...v0.38.0) (2024-10-30)
|
# [0.38.0](https://github.com/sasjs/server/compare/v0.37.0...v0.38.0) (2024-10-30)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
sessionId:
|
sessionId:
|
||||||
type: string
|
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."
|
description: "`sessionId` is the ID of the session and the name of the temporary folder\nused to store code outputs.<br><br>\nFor SAS, this would be the location of the SASWORK folder.<br><br>\n`sessionId` can be used to poll session state using the\nGET /SASjsApi/session/{sessionId}/state endpoint."
|
||||||
example: '{ sessionId: ''20241028074744-54132-1730101664824'' }'
|
example: 20241028074744-54132-1730101664824
|
||||||
required:
|
required:
|
||||||
- sessionId
|
- sessionId
|
||||||
type: object
|
type: object
|
||||||
@@ -585,6 +585,14 @@ components:
|
|||||||
- needsToUpdatePassword
|
- needsToUpdatePassword
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
SessionState:
|
||||||
|
enum:
|
||||||
|
- initialising
|
||||||
|
- pending
|
||||||
|
- running
|
||||||
|
- completed
|
||||||
|
- failed
|
||||||
|
type: string
|
||||||
ExecutePostRequestPayload:
|
ExecutePostRequestPayload:
|
||||||
properties:
|
properties:
|
||||||
_program:
|
_program:
|
||||||
@@ -597,8 +605,8 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
sessionId:
|
sessionId:
|
||||||
type: string
|
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."
|
description: "`sessionId` is the ID of the session and the name of the temporary folder\nused to store program outputs.<br><br>\nFor SAS, this would be the location of the SASWORK folder.<br><br>\n`sessionId` can be used to poll session state using the\nGET /SASjsApi/session/{sessionId}/state endpoint."
|
||||||
example: '{ sessionId: ''20241028074744-54132-1730101664824'' }'
|
example: 20241028074744-54132-1730101664824
|
||||||
required:
|
required:
|
||||||
- sessionId
|
- sessionId
|
||||||
type: object
|
type: object
|
||||||
@@ -1841,6 +1849,30 @@ paths:
|
|||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
parameters: []
|
parameters: []
|
||||||
|
'/SASjsApi/session/{sessionId}/state':
|
||||||
|
get:
|
||||||
|
operationId: SessionState
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SessionState'
|
||||||
|
description: "The polling endpoint is currently implemented for single-server deployments only.<br>\nLoad balanced / grid topologies will be supported in a future release.<br>\nIf your site requires this, please reach out to SASjs Support."
|
||||||
|
summary: 'Get session state (initialising, pending, running, completed, failed).'
|
||||||
|
tags:
|
||||||
|
- Session
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
in: path
|
||||||
|
name: sessionId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
/SASjsApi/stp/execute:
|
/SASjsApi/stp/execute:
|
||||||
get:
|
get:
|
||||||
operationId: ExecuteGetRequest
|
operationId: ExecuteGetRequest
|
||||||
|
|||||||
@@ -42,10 +42,12 @@ interface TriggerCodePayload {
|
|||||||
|
|
||||||
interface TriggerCodeResponse {
|
interface TriggerCodeResponse {
|
||||||
/**
|
/**
|
||||||
* The SessionId is the name of the temporary folder used to store the outputs.
|
* `sessionId` is the ID of the session and the name of the temporary folder
|
||||||
* For SAS, this would be the SASWORK folder. Can be used to poll job status.
|
* used to store code outputs.<br><br>
|
||||||
* This session ID should be used to poll job status.
|
* For SAS, this would be the location of the SASWORK folder.<br><br>
|
||||||
* @example "{ sessionId: '20241028074744-54132-1730101664824' }"
|
* `sessionId` can be used to poll session state using the
|
||||||
|
* GET /SASjsApi/session/{sessionId}/state endpoint.
|
||||||
|
* @example "20241028074744-54132-1730101664824"
|
||||||
*/
|
*/
|
||||||
sessionId: string
|
sessionId: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import path from 'path'
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { getSessionController, processProgram } from './'
|
import { getSessionController, processProgram } from './'
|
||||||
import { readFile, fileExists, createFile, readFileBinary } from '@sasjs/utils'
|
import { readFile, fileExists, createFile, readFileBinary } from '@sasjs/utils'
|
||||||
import { PreProgramVars, Session, TreeNode } from '../../types'
|
import { PreProgramVars, Session, TreeNode, SessionState } from '../../types'
|
||||||
import {
|
import {
|
||||||
extractHeaders,
|
extractHeaders,
|
||||||
getFilesFolder,
|
getFilesFolder,
|
||||||
@@ -75,8 +75,7 @@ export class ExecutionController {
|
|||||||
|
|
||||||
const session =
|
const session =
|
||||||
sessionByFileUpload ?? (await sessionController.getSession())
|
sessionByFileUpload ?? (await sessionController.getSession())
|
||||||
session.inUse = true
|
session.state = SessionState.running
|
||||||
session.consumed = true
|
|
||||||
|
|
||||||
const logPath = path.join(session.path, 'log.log')
|
const logPath = path.join(session.path, 'log.log')
|
||||||
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
||||||
@@ -121,7 +120,7 @@ export class ExecutionController {
|
|||||||
: ''
|
: ''
|
||||||
|
|
||||||
// it should be deleted by scheduleSessionDestroy
|
// it should be deleted by scheduleSessionDestroy
|
||||||
session.inUse = false
|
session.state = SessionState.completed
|
||||||
|
|
||||||
const resultParts = []
|
const resultParts = []
|
||||||
|
|
||||||
@@ -145,7 +144,9 @@ export class ExecutionController {
|
|||||||
return {
|
return {
|
||||||
httpHeaders,
|
httpHeaders,
|
||||||
result:
|
result:
|
||||||
isDebugOn(vars) || session.crashed ? resultParts.join(`\n`) : webout
|
isDebugOn(vars) || session.failureReason
|
||||||
|
? resultParts.join(`\n`)
|
||||||
|
: webout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,8 @@ import { Request, RequestHandler } from 'express'
|
|||||||
import multer from 'multer'
|
import multer from 'multer'
|
||||||
import { uuidv4 } from '@sasjs/utils'
|
import { uuidv4 } from '@sasjs/utils'
|
||||||
import { getSessionController } from '.'
|
import { getSessionController } from '.'
|
||||||
import {
|
import { executeProgramRawValidation, getRunTimeAndFilePath } from '../../utils'
|
||||||
executeProgramRawValidation,
|
import { SessionState } from '../../types'
|
||||||
getRunTimeAndFilePath,
|
|
||||||
RunTimeType
|
|
||||||
} from '../../utils'
|
|
||||||
|
|
||||||
export class FileUploadController {
|
export class FileUploadController {
|
||||||
private storage = multer.diskStorage({
|
private storage = multer.diskStorage({
|
||||||
@@ -56,9 +53,8 @@ export class FileUploadController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const session = await sessionController.getSession()
|
const session = await sessionController.getSession()
|
||||||
// marking consumed true, so that it's not available
|
// change session state to 'running', so that it's not available for any other request
|
||||||
// as readySession for any other request
|
session.state = SessionState.running
|
||||||
session.consumed = true
|
|
||||||
|
|
||||||
req.sasjsSession = session
|
req.sasjsSession = session
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { Session } from '../../types'
|
import { Session, SessionState } from '../../types'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { execFile } from 'child_process'
|
import { execFile } from 'child_process'
|
||||||
import {
|
import {
|
||||||
@@ -23,7 +23,9 @@ export class SessionController {
|
|||||||
protected sessions: Session[] = []
|
protected sessions: Session[] = []
|
||||||
|
|
||||||
protected getReadySessions = (): Session[] =>
|
protected getReadySessions = (): Session[] =>
|
||||||
this.sessions.filter((sess: Session) => sess.ready && !sess.consumed)
|
this.sessions.filter(
|
||||||
|
(session: Session) => session.state === SessionState.pending
|
||||||
|
)
|
||||||
|
|
||||||
protected async createSession(): Promise<Session> {
|
protected async createSession(): Promise<Session> {
|
||||||
const sessionId = generateUniqueFileName(generateTimestamp())
|
const sessionId = generateUniqueFileName(generateTimestamp())
|
||||||
@@ -39,19 +41,18 @@ export class SessionController {
|
|||||||
|
|
||||||
const session: Session = {
|
const session: Session = {
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
ready: true,
|
state: SessionState.pending,
|
||||||
inUse: true,
|
|
||||||
consumed: false,
|
|
||||||
completed: false,
|
|
||||||
creationTimeStamp,
|
creationTimeStamp,
|
||||||
deathTimeStamp,
|
deathTimeStamp,
|
||||||
path: sessionFolder
|
path: sessionFolder
|
||||||
}
|
}
|
||||||
|
|
||||||
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
const headersPath = path.join(session.path, 'stpsrv_header.txt')
|
||||||
|
|
||||||
await createFile(headersPath, 'content-type: text/html; charset=utf-8')
|
await createFile(headersPath, 'content-type: text/html; charset=utf-8')
|
||||||
|
|
||||||
this.sessions.push(session)
|
this.sessions.push(session)
|
||||||
|
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +67,10 @@ export class SessionController {
|
|||||||
|
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getSessionById(id: string) {
|
||||||
|
return this.sessions.find((session) => session.id === id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SASSessionController extends SessionController {
|
export class SASSessionController extends SessionController {
|
||||||
@@ -83,10 +88,7 @@ export class SASSessionController extends SessionController {
|
|||||||
|
|
||||||
const session: Session = {
|
const session: Session = {
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
ready: false,
|
state: SessionState.initialising,
|
||||||
inUse: false,
|
|
||||||
consumed: false,
|
|
||||||
completed: false,
|
|
||||||
creationTimeStamp,
|
creationTimeStamp,
|
||||||
deathTimeStamp,
|
deathTimeStamp,
|
||||||
path: sessionFolder
|
path: sessionFolder
|
||||||
@@ -144,13 +146,20 @@ ${autoExecContent}`
|
|||||||
process.sasLoc!.endsWith('sas.exe') ? session.path : ''
|
process.sasLoc!.endsWith('sas.exe') ? session.path : ''
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
session.completed = true
|
session.state = SessionState.completed
|
||||||
|
|
||||||
process.logger.info('session completed', session)
|
process.logger.info('session completed', session)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
session.completed = true
|
session.state = SessionState.failed
|
||||||
session.crashed = err.toString()
|
|
||||||
process.logger.error('session crashed', session.id, session.crashed)
|
session.failureReason = err.toString()
|
||||||
|
|
||||||
|
process.logger.error(
|
||||||
|
'session crashed',
|
||||||
|
session.id,
|
||||||
|
session.failureReason
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// we have a triggered session - add to array
|
// we have a triggered session - add to array
|
||||||
@@ -167,15 +176,19 @@ ${autoExecContent}`
|
|||||||
const codeFilePath = path.join(session.path, 'code.sas')
|
const codeFilePath = path.join(session.path, 'code.sas')
|
||||||
|
|
||||||
// TODO: don't wait forever
|
// TODO: don't wait forever
|
||||||
while ((await fileExists(codeFilePath)) && !session.crashed) {}
|
while (
|
||||||
|
(await fileExists(codeFilePath)) &&
|
||||||
|
session.state !== SessionState.failed
|
||||||
|
) {}
|
||||||
|
|
||||||
if (session.crashed)
|
if (session.state === SessionState.failed) {
|
||||||
process.logger.error(
|
process.logger.error(
|
||||||
'session crashed! while waiting to be ready',
|
'session crashed! while waiting to be ready',
|
||||||
session.crashed
|
session.failureReason
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
session.ready = true
|
session.state = SessionState.pending
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async deleteSession(session: Session) {
|
private async deleteSession(session: Session) {
|
||||||
@@ -191,7 +204,7 @@ ${autoExecContent}`
|
|||||||
private scheduleSessionDestroy(session: Session) {
|
private scheduleSessionDestroy(session: Session) {
|
||||||
setTimeout(
|
setTimeout(
|
||||||
async () => {
|
async () => {
|
||||||
if (session.inUse) {
|
if (session.state === SessionState.running) {
|
||||||
// adding 10 more minutes
|
// adding 10 more minutes
|
||||||
const newDeathTimeStamp =
|
const newDeathTimeStamp =
|
||||||
parseInt(session.deathTimeStamp) + 10 * 60 * 1000
|
parseInt(session.deathTimeStamp) + 10 * 60 * 1000
|
||||||
@@ -202,7 +215,7 @@ ${autoExecContent}`
|
|||||||
const { expiresAfterMins } = session
|
const { expiresAfterMins } = session
|
||||||
|
|
||||||
// delay session destroy if expiresAfterMins present
|
// delay session destroy if expiresAfterMins present
|
||||||
if (expiresAfterMins && !expiresAfterMins.used) {
|
if (expiresAfterMins && session.state !== SessionState.completed) {
|
||||||
// calculate session death time using expiresAfterMins
|
// calculate session death time using expiresAfterMins
|
||||||
const newDeathTimeStamp =
|
const newDeathTimeStamp =
|
||||||
parseInt(session.deathTimeStamp) +
|
parseInt(session.deathTimeStamp) +
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { WriteStream, createWriteStream } from 'fs'
|
|||||||
import { execFile } from 'child_process'
|
import { execFile } from 'child_process'
|
||||||
import { once } from 'stream'
|
import { once } from 'stream'
|
||||||
import { createFile, moveFile } from '@sasjs/utils'
|
import { createFile, moveFile } from '@sasjs/utils'
|
||||||
import { PreProgramVars, Session } from '../../types'
|
import { PreProgramVars, Session, SessionState } from '../../types'
|
||||||
import { RunTimeType } from '../../utils'
|
import { RunTimeType } from '../../utils'
|
||||||
import {
|
import {
|
||||||
ExecutionVars,
|
ExecutionVars,
|
||||||
@@ -49,7 +49,7 @@ export const processProgram = async (
|
|||||||
await moveFile(codePath + '.bkp', codePath)
|
await moveFile(codePath + '.bkp', codePath)
|
||||||
|
|
||||||
// we now need to poll the session status
|
// we now need to poll the session status
|
||||||
while (!session.completed) {
|
while (session.state !== SessionState.completed) {
|
||||||
await delay(50)
|
await delay(50)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -114,13 +114,20 @@ export const processProgram = async (
|
|||||||
|
|
||||||
await execFilePromise(executablePath, [codePath], writeStream)
|
await execFilePromise(executablePath, [codePath], writeStream)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
session.completed = true
|
session.state = SessionState.completed
|
||||||
|
|
||||||
process.logger.info('session completed', session)
|
process.logger.info('session completed', session)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
session.completed = true
|
session.state = SessionState.failed
|
||||||
session.crashed = err.toString()
|
|
||||||
process.logger.error('session crashed', session.id, session.crashed)
|
session.failureReason = err.toString()
|
||||||
|
|
||||||
|
process.logger.error(
|
||||||
|
'session crashed',
|
||||||
|
session.id,
|
||||||
|
session.failureReason
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// copy the code file to log and end write stream
|
// copy the code file to log and end write stream
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { Request, Security, Route, Tags, Example, Get } from 'tsoa'
|
import { Request, Security, Route, Tags, Example, Get } from 'tsoa'
|
||||||
import { UserResponse } from './user'
|
import { UserResponse } from './user'
|
||||||
|
import { getSessionController } from './internal'
|
||||||
|
import { SessionState } from '../types'
|
||||||
|
|
||||||
interface SessionResponse extends UserResponse {
|
interface SessionResponse extends UserResponse {
|
||||||
needsToUpdatePassword: boolean
|
needsToUpdatePassword: boolean
|
||||||
@@ -26,6 +28,18 @@ export class SessionController {
|
|||||||
): Promise<SessionResponse> {
|
): Promise<SessionResponse> {
|
||||||
return session(request)
|
return session(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The polling endpoint is currently implemented for single-server deployments only.<br>
|
||||||
|
* Load balanced / grid topologies will be supported in a future release.<br>
|
||||||
|
* If your site requires this, please reach out to SASjs Support.
|
||||||
|
* @summary Get session state (initialising, pending, running, completed, failed).
|
||||||
|
* @example completed
|
||||||
|
*/
|
||||||
|
@Get('/:sessionId/state')
|
||||||
|
public async sessionState(sessionId: string): Promise<SessionState> {
|
||||||
|
return sessionState(sessionId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = (req: express.Request) => ({
|
const session = (req: express.Request) => ({
|
||||||
@@ -35,3 +49,23 @@ const session = (req: express.Request) => ({
|
|||||||
isAdmin: req.user!.isAdmin,
|
isAdmin: req.user!.isAdmin,
|
||||||
needsToUpdatePassword: req.user!.needsToUpdatePassword
|
needsToUpdatePassword: req.user!.needsToUpdatePassword
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const sessionState = (sessionId: string): SessionState => {
|
||||||
|
for (let runTime of process.runTimes) {
|
||||||
|
// get session controller for each available runTime
|
||||||
|
const sessionController = getSessionController(runTime)
|
||||||
|
|
||||||
|
// get session by sessionId
|
||||||
|
const session = sessionController.getSessionById(sessionId)
|
||||||
|
|
||||||
|
// return session state if session was found
|
||||||
|
if (session) {
|
||||||
|
return session.state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw {
|
||||||
|
code: 404,
|
||||||
|
message: `Session with ID '${sessionId}' was not found.`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,10 +40,12 @@ interface TriggerProgramPayload {
|
|||||||
|
|
||||||
interface TriggerProgramResponse {
|
interface TriggerProgramResponse {
|
||||||
/**
|
/**
|
||||||
* The SessionId is the name of the temporary folder used to store the outputs.
|
* `sessionId` is the ID of the session and the name of the temporary folder
|
||||||
* For SAS, this would be the SASWORK folder. Can be used to poll program status.
|
* used to store program outputs.<br><br>
|
||||||
* This session ID should be used to poll program status.
|
* For SAS, this would be the location of the SASWORK folder.<br><br>
|
||||||
* @example "{ sessionId: '20241028074744-54132-1730101664824' }"
|
* `sessionId` can be used to poll session state using the
|
||||||
|
* GET /SASjsApi/session/{sessionId}/state endpoint.
|
||||||
|
* @example "20241028074744-54132-1730101664824"
|
||||||
*/
|
*/
|
||||||
sessionId: string
|
sessionId: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,37 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { SessionController } from '../../controllers'
|
import { SessionController } from '../../controllers'
|
||||||
|
import { sessionIdValidation } from '../../utils'
|
||||||
|
|
||||||
const sessionRouter = express.Router()
|
const sessionRouter = express.Router()
|
||||||
|
|
||||||
|
const controller = new SessionController()
|
||||||
|
|
||||||
sessionRouter.get('/', async (req, res) => {
|
sessionRouter.get('/', async (req, res) => {
|
||||||
const controller = new SessionController()
|
|
||||||
try {
|
try {
|
||||||
const response = await controller.session(req)
|
const response = await controller.session(req)
|
||||||
|
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
sessionRouter.get('/:sessionId/state', async (req, res) => {
|
||||||
|
const { error, value: params } = sessionIdValidation(req.params)
|
||||||
|
if (error) return res.status(400).send(error.details[0].message)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await controller.sessionState(params.sessionId)
|
||||||
|
|
||||||
|
res.status(200)
|
||||||
|
res.send(response)
|
||||||
|
} catch (err: any) {
|
||||||
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default sessionRouter
|
export default sessionRouter
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
SASSessionController
|
SASSessionController
|
||||||
} from '../../../controllers/internal'
|
} from '../../../controllers/internal'
|
||||||
import * as ProcessProgramModule from '../../../controllers/internal/processProgram'
|
import * as ProcessProgramModule from '../../../controllers/internal/processProgram'
|
||||||
import { Session } from '../../../types'
|
import { Session, SessionState } from '../../../types'
|
||||||
|
|
||||||
const clientId = 'someclientID'
|
const clientId = 'someclientID'
|
||||||
|
|
||||||
@@ -493,10 +493,7 @@ const mockedGetSession = async () => {
|
|||||||
|
|
||||||
const session: Session = {
|
const session: Session = {
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
ready: true,
|
state: SessionState.pending,
|
||||||
inUse: true,
|
|
||||||
consumed: false,
|
|
||||||
completed: false,
|
|
||||||
creationTimeStamp,
|
creationTimeStamp,
|
||||||
deathTimeStamp,
|
deathTimeStamp,
|
||||||
path: sessionFolder
|
path: sessionFolder
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
|
export enum SessionState {
|
||||||
|
initialising = 'initialising', // session is initialising and not ready to be used yet
|
||||||
|
pending = 'pending', // session is ready to be used
|
||||||
|
running = 'running', // session is in use
|
||||||
|
completed = 'completed', // session is completed and can be destroyed
|
||||||
|
failed = 'failed' // session failed
|
||||||
|
}
|
||||||
export interface Session {
|
export interface Session {
|
||||||
id: string
|
id: string
|
||||||
ready: boolean
|
state: SessionState
|
||||||
creationTimeStamp: string
|
creationTimeStamp: string
|
||||||
deathTimeStamp: string
|
deathTimeStamp: string
|
||||||
path: string
|
path: string
|
||||||
inUse: boolean
|
|
||||||
consumed: boolean
|
|
||||||
completed: boolean
|
|
||||||
crashed?: string
|
|
||||||
expiresAfterMins?: { mins: number; used: boolean }
|
expiresAfterMins?: { mins: number; used: boolean }
|
||||||
|
failureReason?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,3 +201,8 @@ export const triggerProgramValidation = (data: any): Joi.ValidationResult =>
|
|||||||
})
|
})
|
||||||
.pattern(/^/, Joi.alternatives(Joi.string(), Joi.number()))
|
.pattern(/^/, Joi.alternatives(Joi.string(), Joi.number()))
|
||||||
.validate(data)
|
.validate(data)
|
||||||
|
|
||||||
|
export const sessionIdValidation = (data: any): Joi.ValidationResult =>
|
||||||
|
Joi.object({
|
||||||
|
sessionId: Joi.string().required()
|
||||||
|
}).validate(data)
|
||||||
|
|||||||
Reference in New Issue
Block a user