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

Compare commits

..

6 Commits

Author SHA1 Message Date
Saad Jutt
99fb5f4b2b chore(release): 0.0.33 2022-03-16 06:44:35 +05:00
Muhammad Saad
5dc3deeb11 Merge pull request #86 from sasjs/issue-80
Issue 80
2022-03-16 06:24:41 +05:00
Saad Jutt
6b708fcad3 fix: added api button on web component 2022-03-16 06:22:26 +05:00
Saad Jutt
bc0ff84d8d chore: updated code as per sasjs/utils breaking change 2022-03-16 05:04:33 +05:00
Saad Jutt
1ff6965dd2 fix: adde validation + code improvement 2022-03-16 04:53:07 +05:00
Saad Jutt
d6fa877941 feat: serve deployed streaming apps 2022-03-15 03:54:19 +05:00
20 changed files with 434 additions and 161 deletions

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
v16.14.0

View File

@@ -2,6 +2,19 @@
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.33](https://github.com/sasjs/server/compare/v0.0.32...v0.0.33) (2022-03-16)
### Features
* serve deployed streaming apps ([d6fa877](https://github.com/sasjs/server/commit/d6fa87794155880adc23c2552c37c86ad606c292))
### Bug Fixes
* adde validation + code improvement ([1ff6965](https://github.com/sasjs/server/commit/1ff6965dd2f44ad74136af04b4fba8c76979ecba))
* added api button on web component ([6b708fc](https://github.com/sasjs/server/commit/6b708fcad30d92c21713f9c97bca173c148cc875))
### [0.0.32](https://github.com/sasjs/server/compare/v0.0.31...v0.0.32) (2022-03-14)

1
api/.nvmrc Normal file
View File

@@ -0,0 +1 @@
v16.14.0

408
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -46,7 +46,7 @@
"author": "4GL Ltd",
"dependencies": {
"@sasjs/core": "4.9.0",
"@sasjs/utils": "2.34.1",
"@sasjs/utils": "2.36.2",
"bcryptjs": "^2.4.3",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
@@ -57,8 +57,7 @@
"mongoose-sequence": "^5.3.1",
"morgan": "^1.10.0",
"multer": "^1.4.3",
"swagger-ui-express": "^4.1.6",
"tsoa": "3.14.1"
"swagger-ui-express": "^4.1.6"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.2",
@@ -84,6 +83,7 @@
"supertest": "^6.1.3",
"ts-jest": "^27.0.3",
"ts-node": "^10.0.0",
"tsoa": "3.14.1",
"typescript": "^4.3.2"
},
"configuration": {

View File

@@ -172,12 +172,20 @@ components:
enum:
- service
type: string
MemberType.file:
enum:
- file
type: string
ServiceMember:
properties:
name:
type: string
type:
$ref: '#/components/schemas/MemberType.service'
anyOf:
-
$ref: '#/components/schemas/MemberType.service'
-
$ref: '#/components/schemas/MemberType.file'
code:
type: string
required:
@@ -220,6 +228,7 @@ components:
fileTree:
$ref: '#/components/schemas/FileTree'
required:
- appLoc
- fileTree
type: object
additionalProperties: false

View File

@@ -17,6 +17,7 @@ const compiledSystemInit = async (systemInit: string) =>
programFolders: [],
macroFolders: [],
buildSourceFolder: '',
binaryFolders: [],
macroCorePath
}))

View File

@@ -28,7 +28,7 @@ import { FileTree, isFileTree, TreeNode } from '../types'
import { getTmpFilesFolderPath } from '../utils'
interface DeployPayload {
appLoc?: string
appLoc: string
fileTree: FileTree
}
@@ -196,7 +196,7 @@ const deploy = async (data: DeployPayload) => {
await createFileTree(
data.fileTree.members,
data.appLoc ? data.appLoc.replace(/^\//, '').split('/') : []
data.appLoc.replace(/^\//, '').split('/')
).catch((err) => {
throw { code: 500, ...execDeployErrorResponse, ...err }
})

View File

@@ -1,7 +1,7 @@
import path from 'path'
import { MemberType, FolderMember, ServiceMember, FileTree } from '../../types'
import { getTmpFilesFolderPath } from '../../utils/file'
import { createFolder, createFile, asyncForEach } from '@sasjs/utils'
import path from 'path'
// REFACTOR: export FileTreeCpntroller
export const createFileTree = async (
@@ -27,9 +27,13 @@ export const createFileTree = async (
(err) => Promise.reject({ error: err, failedToCreate: name })
)
} else {
await createFile(path.join(destinationPath, name), member.code).catch(
(err) => Promise.reject({ error: err, failedToCreate: name })
)
const encoding = member.type === MemberType.file ? 'base64' : undefined
await createFile(
path.join(destinationPath, name),
member.code,
encoding
).catch((err) => Promise.reject({ error: err, failedToCreate: name }))
}
})

View File

@@ -1,17 +1,31 @@
import express from 'express'
import { deleteFile } from '@sasjs/utils'
import { publishAppStream } from '../appStream'
import { multerSingle } from '../../middlewares/multer'
import { DriveController } from '../../controllers/'
import { fileBodyValidation, fileParamValidation } from '../../utils'
import {
deployValidation,
fileBodyValidation,
fileParamValidation
} from '../../utils'
const controller = new DriveController()
const driveRouter = express.Router()
driveRouter.post('/deploy', async (req, res) => {
const { error, value: body } = deployValidation(req.body)
if (error) return res.status(400).send(error.details[0].message)
try {
const response = await controller.deploy(req.body)
const response = await controller.deploy(body)
const appLoc = body.appLoc.replace(/^\//, '')?.split('/')
publishAppStream(appLoc)
res.send(response)
} catch (err: any) {
const statusCode = err.code

View File

@@ -74,14 +74,19 @@ describe('files', () => {
const res = await request(app)
.post('/SASjsApi/drive/deploy')
.auth(accessToken, { type: 'bearer' })
.send(payload)
.send({ appLoc: '/Public', fileTree: payload })
expect(res.statusCode).toEqual(400)
expect(res.body).toEqual({
status: 'failure',
message: 'Provided not supported data format.',
example: getTreeExample()
})
if (payload === undefined) {
expect(res.text).toEqual('"fileTree" is required')
} else {
expect(res.body).toEqual({
status: 'failure',
message: 'Provided not supported data format.',
example: getTreeExample()
})
}
}
it('should respond with payload example if valid payload was not provided', async () => {
@@ -140,11 +145,11 @@ describe('files', () => {
})
})
it('should respond with payload example if valid payload was not provided', async () => {
it('should successfully deploy if valid payload was provided', async () => {
const res = await request(app)
.post('/SASjsApi/drive/deploy')
.auth(accessToken, { type: 'bearer' })
.send({ fileTree: getTreeExample() })
.send({ appLoc: '/public', fileTree: getTreeExample() })
expect(res.statusCode).toEqual(200)
expect(res.text).toEqual(
@@ -154,6 +159,7 @@ describe('files', () => {
const testJobFolder = path.join(
getTmpFilesFolderPath(),
'public',
'jobs',
'extract'
)

View File

@@ -0,0 +1,26 @@
import path from 'path'
import express from 'express'
import { folderExists } from '@sasjs/utils'
import { getTmpFilesFolderPath } from '../../utils'
const router = express.Router()
export const publishAppStream = async (appLoc: string[]) => {
const appLocUrl = encodeURI(appLoc.join('/'))
const appLocPath = appLoc.join(path.sep)
const pathToDeployment = path.join(
getTmpFilesFolderPath(),
appLocPath,
'services',
'webv'
)
if (await folderExists(pathToDeployment)) {
router.use(`/${appLocUrl}`, express.static(pathToDeployment))
console.log('Serving Stream App: ', appLocUrl)
}
}
export default router

View File

@@ -2,8 +2,15 @@ import { Express } from 'express'
import webRouter from './web'
import apiRouter from './api'
import appStreamRouter from './appStream'
export const setupRoutes = (app: Express) => {
app.use('/', webRouter)
app.use('/SASjsApi', apiRouter)
app.use('/AppStream', function (req, res, next) {
// this needs to be a function to hook on
// whatever the current router is
appStreamRouter(req, res, next)
})
}

View File

@@ -4,7 +4,8 @@ export interface FileTree {
export enum MemberType {
folder = 'folder',
service = 'service'
service = 'service',
file = 'file'
}
export interface FolderMember {
@@ -15,7 +16,7 @@ export interface FolderMember {
export interface ServiceMember {
name: string
type: MemberType.service
type: MemberType.service | MemberType.file
code: string
}
@@ -36,7 +37,9 @@ const isFolderMember = (arg: any): arg is FolderMember =>
Array.isArray(arg.members) &&
arg.members.filter(
(member: FolderMember | ServiceMember) =>
!isFolderMember(member) && !isServiceMember(member)
!isFolderMember(member) &&
!isServiceMember(member) &&
!isFileMember(member)
).length === 0
const isServiceMember = (arg: any): arg is ServiceMember =>
@@ -45,3 +48,10 @@ const isServiceMember = (arg: any): arg is ServiceMember =>
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

@@ -66,6 +66,12 @@ export const registerClientValidation = (data: any): Joi.ValidationResult =>
clientSecret: Joi.string().required()
}).validate(data)
export const deployValidation = (data: any): Joi.ValidationResult =>
Joi.object({
appLoc: Joi.string().pattern(/^\//).required().min(2),
fileTree: Joi.any().required()
}).validate(data)
export const fileBodyValidation = (data: any): Joi.ValidationResult =>
Joi.object({
filePath: Joi.string().pattern(/.sas$/).required().messages({

4
package-lock.json generated
View File

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

View File

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

1
web/.nvmrc Normal file
View File

@@ -0,0 +1 @@
v16.14.0

17
web/package-lock.json generated
View File

@@ -9215,11 +9215,14 @@
}
},
"node_modules/prismjs": {
"version": "1.25.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz",
"integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg==",
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
"dev": true,
"peer": true
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/process": {
"version": "0.11.10",
@@ -18181,9 +18184,9 @@
}
},
"prismjs": {
"version": "1.25.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz",
"integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg==",
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
"dev": true,
"peer": true
},

View File

@@ -5,6 +5,13 @@ import AppBar from '@mui/material/AppBar'
import Toolbar from '@mui/material/Toolbar'
import Tabs from '@mui/material/Tabs'
import Tab from '@mui/material/Tab'
import Button from '@mui/material/Button'
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
const NODE_ENV = process.env.NODE_ENV
const PORT_API = process.env.PORT_API
const baseUrl =
NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : ''
const Header = (props: any) => {
const history = useHistory()
@@ -52,6 +59,18 @@ const Header = (props: any) => {
component={Link}
/>
</Tabs>
<Button
href={`${baseUrl}/SASjsApi`}
target="_blank"
rel="noreferrer"
variant="contained"
color="primary"
size="large"
startIcon={<OpenInNewIcon />}
style={{ marginLeft: '50px' }}
>
API Docs
</Button>
</Toolbar>
</AppBar>
)