mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 11:24:35 +00:00
1
api/.nvmrc
Normal file
1
api/.nvmrc
Normal file
@@ -0,0 +1 @@
|
||||
v16.14.0
|
||||
408
api/package-lock.json
generated
408
api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,6 +17,7 @@ const compiledSystemInit = async (systemInit: string) =>
|
||||
programFolders: [],
|
||||
macroFolders: [],
|
||||
buildSourceFolder: '',
|
||||
binaryFolders: [],
|
||||
macroCorePath
|
||||
}))
|
||||
|
||||
|
||||
@@ -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 }
|
||||
})
|
||||
|
||||
@@ -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 }))
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
|
||||
26
api/src/routes/appStream/index.ts
Normal file
26
api/src/routes/appStream/index.ts
Normal 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
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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({
|
||||
|
||||
1
web/.nvmrc
Normal file
1
web/.nvmrc
Normal file
@@ -0,0 +1 @@
|
||||
v16.14.0
|
||||
17
web/package-lock.json
generated
17
web/package-lock.json
generated
@@ -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
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user