mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 19:34:34 +00:00
Compare commits
10 Commits
v0.0.9
...
mock-viya-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcef9a4a9d | ||
|
|
a4d5ee99c4 | ||
|
|
d7e835778b | ||
|
|
d7d3bb285f | ||
|
|
d532d74879 | ||
|
|
34e54934fd | ||
|
|
4873e6054f | ||
|
|
b00aa4e17b | ||
|
|
9fccfe6f35 | ||
|
|
43545fa04b |
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
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.10](https://github.com/sasjs/server/compare/v0.0.9...v0.0.10) (2021-12-07)
|
||||||
|
|
||||||
### [0.0.9](https://github.com/sasjs/server/compare/v0.0.3...v0.0.9) (2021-12-07)
|
### [0.0.9](https://github.com/sasjs/server/compare/v0.0.3...v0.0.9) (2021-12-07)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
FROM node:lts-alpine
|
FROM node:lts-alpine
|
||||||
RUN npm install -g @sasjs/cli
|
|
||||||
WORKDIR /usr/server/api
|
WORKDIR /usr/server/api
|
||||||
COPY ["package.json","package-lock.json", "./"]
|
COPY ["package.json","package-lock.json", "./"]
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|||||||
@@ -59,12 +59,12 @@ It will build following images if running first time:
|
|||||||
|
|
||||||
### Using node:
|
### Using node:
|
||||||
|
|
||||||
#### Development (running api and web seperately):
|
#### Development (running api and web separately):
|
||||||
|
|
||||||
##### API
|
##### API
|
||||||
|
|
||||||
Navigate to `./api`
|
Navigate to `./api`
|
||||||
There is `.env.example` file present at `./api` directory. Remember to provide enviornment variables else default values will be used mentioned in `.env.example` files
|
There is `.env.example` file present at `./api` directory. Remember to provide environment variables else default values will be used mentioned in `.env.example` files
|
||||||
Command to install and run api server.
|
Command to install and run api server.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -362,14 +362,15 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
log:
|
|
||||||
type: string
|
|
||||||
_webout:
|
_webout:
|
||||||
type: string
|
type: string
|
||||||
|
log:
|
||||||
|
type: string
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- status
|
- status
|
||||||
|
- _webout
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
ExecuteReturnJsonPayload:
|
ExecuteReturnJsonPayload:
|
||||||
@@ -981,7 +982,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
description: "Trigger a SAS program using it's location in the _program parameter.\r\nEnable debugging using the _debug parameter.\r\nAdditional URL parameters are turned into SAS macro variables.\r\nAny files provided are placed into the session and\r\ncorresponding _WEBIN_XXX variables are created."
|
description: "Trigger a SAS program using it's location in the _program parameter.\nEnable debugging using the _debug parameter.\nAdditional URL parameters are turned into SAS macro variables.\nAny files provided are placed into the session and\ncorresponding _WEBIN_XXX variables are created."
|
||||||
summary: 'Execute Stored Program, return raw content'
|
summary: 'Execute Stored Program, return raw content'
|
||||||
tags:
|
tags:
|
||||||
- STP
|
- STP
|
||||||
@@ -1005,7 +1006,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ExecuteReturnJsonResponse'
|
$ref: '#/components/schemas/ExecuteReturnJsonResponse'
|
||||||
description: "Trigger a SAS program using it's location in the _program parameter.\r\nEnable debugging using the _debug parameter.\r\nAdditional URL parameters are turned into SAS macro variables.\r\nAny files provided are placed into the session and\r\ncorresponding _WEBIN_XXX variables are created."
|
description: "Trigger a SAS program using it's location in the _program parameter.\nEnable debugging using the _debug parameter.\nAdditional URL parameters are turned into SAS macro variables.\nAny files provided are placed into the session and\ncorresponding _WEBIN_XXX variables are created."
|
||||||
summary: 'Execute Stored Program, return JSON'
|
summary: 'Execute Stored Program, return JSON'
|
||||||
tags:
|
tags:
|
||||||
- STP
|
- STP
|
||||||
@@ -1026,10 +1027,33 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ExecuteReturnJsonPayload'
|
$ref: '#/components/schemas/ExecuteReturnJsonPayload'
|
||||||
|
/SASjsApi/session:
|
||||||
|
get:
|
||||||
|
operationId: Session
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserResponse'
|
||||||
|
examples:
|
||||||
|
'Example 1':
|
||||||
|
value: {id: 123, username: johnusername, displayName: John}
|
||||||
|
summary: 'Get session info (username).'
|
||||||
|
tags:
|
||||||
|
- Session
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
parameters: []
|
||||||
servers:
|
servers:
|
||||||
-
|
-
|
||||||
url: /
|
url: /
|
||||||
tags:
|
tags:
|
||||||
|
-
|
||||||
|
name: Session
|
||||||
|
description: 'Get Session information'
|
||||||
-
|
-
|
||||||
name: User
|
name: User
|
||||||
description: 'Operations about users'
|
description: 'Operations about users'
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import webRouter from './routes/web'
|
|||||||
import apiRouter from './routes/api'
|
import apiRouter from './routes/api'
|
||||||
import { connectDB, getWebBuildFolderPath } from './utils'
|
import { connectDB, getWebBuildFolderPath } from './utils'
|
||||||
|
|
||||||
|
import { FolderController } from './controllers'
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
@@ -30,4 +32,8 @@ app.use(express.json({ limit: '50mb' }))
|
|||||||
|
|
||||||
app.use(express.static(getWebBuildFolderPath()))
|
app.use(express.static(getWebBuildFolderPath()))
|
||||||
|
|
||||||
|
const folderController = new FolderController()
|
||||||
|
|
||||||
|
folderController.addRootFolder()
|
||||||
|
|
||||||
export default connectDB().then(() => app)
|
export default connectDB().then(() => app)
|
||||||
|
|||||||
69
api/src/controllers/folder.ts
Normal file
69
api/src/controllers/folder.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { Body } from 'tsoa'
|
||||||
|
import Folder, { FolderPayload, MemberType } from '../model/Folder'
|
||||||
|
|
||||||
|
export class FolderController {
|
||||||
|
public async createFolder(@Body() body: FolderPayload) {
|
||||||
|
return createFolder(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addRootFolder() {
|
||||||
|
await addRootFolder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FolderDetailsResponse {
|
||||||
|
name: string
|
||||||
|
parentFolderUri: string
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const createFolder = async ({
|
||||||
|
name,
|
||||||
|
parentFolderUri,
|
||||||
|
type
|
||||||
|
}: FolderPayload): Promise<FolderDetailsResponse> => {
|
||||||
|
parentFolderUri = parentFolderUri.replace(/\/folders\/folders\//i, '')
|
||||||
|
const parentFolder = await Folder.findById(parentFolderUri).catch(
|
||||||
|
(_: any) => {
|
||||||
|
throw new Error(
|
||||||
|
`No folder with an URI '${parentFolderUri}' has been found.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const folder = new Folder({
|
||||||
|
name,
|
||||||
|
parentFolderUri,
|
||||||
|
type
|
||||||
|
})
|
||||||
|
|
||||||
|
const savedFolder = await folder.save().catch((err: any) => {
|
||||||
|
// TODO: log error
|
||||||
|
throw new Error(`Error while saving folder.`)
|
||||||
|
})
|
||||||
|
|
||||||
|
await parentFolder?.addMember(savedFolder._id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: savedFolder.name,
|
||||||
|
parentFolderUri: savedFolder.parentFolderUri,
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addRootFolder = async () => {
|
||||||
|
let folder = await Folder.findOne({ name: '/' })
|
||||||
|
|
||||||
|
if (folder) return
|
||||||
|
|
||||||
|
folder = new Folder({
|
||||||
|
name: '/',
|
||||||
|
parentFolderUri: '',
|
||||||
|
type: MemberType.Folder
|
||||||
|
})
|
||||||
|
folder.parentFolderUri = folder._id
|
||||||
|
|
||||||
|
return await folder.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getItem = async({ path })
|
||||||
@@ -4,3 +4,5 @@ export * from './drive'
|
|||||||
export * from './group'
|
export * from './group'
|
||||||
export * from './stp'
|
export * from './stp'
|
||||||
export * from './user'
|
export * from './user'
|
||||||
|
export * from './session'
|
||||||
|
export * from './folder'
|
||||||
|
|||||||
@@ -100,26 +100,20 @@ ${program}`
|
|||||||
const debugValue =
|
const debugValue =
|
||||||
typeof vars._debug === 'string' ? parseInt(vars._debug) : vars._debug
|
typeof vars._debug === 'string' ? parseInt(vars._debug) : vars._debug
|
||||||
|
|
||||||
let debugResponse: string | undefined
|
|
||||||
|
|
||||||
if ((debugValue && debugValue >= 131) || session.crashed) {
|
|
||||||
debugResponse = `<html><body>${webout}<div style="text-align:left"><hr /><h2>SAS Log</h2><pre>${log}</pre></div></body></html>`
|
|
||||||
}
|
|
||||||
|
|
||||||
session.inUse = false
|
session.inUse = false
|
||||||
sessionController.deleteSession(session)
|
sessionController.deleteSession(session)
|
||||||
|
|
||||||
if (returnJson) {
|
if (returnJson) {
|
||||||
const response: any = {
|
return {
|
||||||
webout: webout
|
webout,
|
||||||
|
log:
|
||||||
|
(debugValue && debugValue >= 131) || session.crashed ? log : undefined
|
||||||
}
|
}
|
||||||
if ((debugValue && debugValue >= 131) || session.crashed) {
|
|
||||||
response.log = log
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
}
|
||||||
return debugResponse ?? webout
|
|
||||||
|
return (debugValue && debugValue >= 131) || session.crashed
|
||||||
|
? `<html><body>${webout}<div style="text-align:left"><hr /><h2>SAS Log</h2><pre>${log}</pre></div></body></html>`
|
||||||
|
: webout
|
||||||
}
|
}
|
||||||
|
|
||||||
buildDirectorytree() {
|
buildDirectorytree() {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export class SessionController {
|
|||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
session.completed = true
|
session.completed = true
|
||||||
session.crashed = err.toString()
|
session.crashed = err.toString()
|
||||||
console.log('session crashed', session.id)
|
console.log('session crashed', session.id, session.crashed)
|
||||||
})
|
})
|
||||||
|
|
||||||
// we have a triggered session - add to array
|
// we have a triggered session - add to array
|
||||||
|
|||||||
30
api/src/controllers/session.ts
Normal file
30
api/src/controllers/session.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import { Request, Security, Route, Tags, Example, Get } from 'tsoa'
|
||||||
|
import { UserResponse } from './user'
|
||||||
|
|
||||||
|
@Security('bearerAuth')
|
||||||
|
@Route('SASjsApi/session')
|
||||||
|
@Tags('Session')
|
||||||
|
export class SessionController {
|
||||||
|
/**
|
||||||
|
* @summary Get session info (username).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Example<UserResponse>({
|
||||||
|
id: 123,
|
||||||
|
username: 'johnusername',
|
||||||
|
displayName: 'John'
|
||||||
|
})
|
||||||
|
@Get('/')
|
||||||
|
public async session(
|
||||||
|
@Request() request: express.Request
|
||||||
|
): Promise<UserResponse> {
|
||||||
|
return session(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = (req: any) => ({
|
||||||
|
id: req.user.id,
|
||||||
|
username: req.user.username,
|
||||||
|
displayName: req.user.displayName
|
||||||
|
})
|
||||||
@@ -1,16 +1,6 @@
|
|||||||
import express, { response } from 'express'
|
import express from 'express'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import {
|
import { Request, Security, Route, Tags, Post, Body, Get, Query } from 'tsoa'
|
||||||
Request,
|
|
||||||
Security,
|
|
||||||
Route,
|
|
||||||
Tags,
|
|
||||||
Example,
|
|
||||||
Post,
|
|
||||||
Body,
|
|
||||||
Get,
|
|
||||||
Query
|
|
||||||
} from 'tsoa'
|
|
||||||
import { ExecutionController } from './internal'
|
import { ExecutionController } from './internal'
|
||||||
import { PreProgramVars } from '../types'
|
import { PreProgramVars } from '../types'
|
||||||
import { getTmpFilesFolderPath, makeFilesNamesMap } from '../utils'
|
import { getTmpFilesFolderPath, makeFilesNamesMap } from '../utils'
|
||||||
@@ -24,8 +14,8 @@ interface ExecuteReturnJsonPayload {
|
|||||||
}
|
}
|
||||||
interface ExecuteReturnJsonResponse {
|
interface ExecuteReturnJsonResponse {
|
||||||
status: string
|
status: string
|
||||||
|
_webout: string
|
||||||
log?: string
|
log?: string
|
||||||
_webout?: string
|
|
||||||
message?: string
|
message?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,17 +101,17 @@ const executeReturnJson = async (
|
|||||||
const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files) : null
|
const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files) : null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const jsonResult: any = await new ExecutionController().execute(
|
const { webout, log } = (await new ExecutionController().execute(
|
||||||
sasCodePath,
|
sasCodePath,
|
||||||
getPreProgramVariables(req),
|
getPreProgramVariables(req),
|
||||||
{ ...req.query, ...req.body },
|
{ ...req.query, ...req.body },
|
||||||
{ filesNamesMap: filesNamesMap },
|
{ filesNamesMap: filesNamesMap },
|
||||||
true
|
true
|
||||||
)
|
)) as { webout: string; log: string }
|
||||||
return {
|
return {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
_webout: jsonResult.webout,
|
_webout: webout,
|
||||||
log: jsonResult.log
|
log
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw {
|
throw {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
|
import { Request, Response } from 'express'
|
||||||
import { verifyTokenInDB } from '../utils'
|
import { verifyTokenInDB } from '../utils'
|
||||||
|
import { headerIsNotPresentMessage, headerIsNotValidMessage } from './header'
|
||||||
|
|
||||||
export const authenticateAccessToken = (req: any, res: any, next: any) => {
|
export const authenticateAccessToken = (req: any, res: any, next: any) => {
|
||||||
authenticateToken(
|
authenticateToken(
|
||||||
@@ -21,12 +23,24 @@ export const authenticateRefreshToken = (req: any, res: any, next: any) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const verifyAuthHeaderIsPresent = (req: Request, res: Response) => {
|
||||||
|
console.log(`🤖[verifyAuthHeaderIsPresent]🤖`)
|
||||||
|
|
||||||
|
const authHeader = req.headers.authorization
|
||||||
|
|
||||||
|
if (!authHeader) {
|
||||||
|
return res.status(401).json(headerIsNotPresentMessage('Authorization'))
|
||||||
|
} else if (!/^Bearer\s.{1}/.test(authHeader)) {
|
||||||
|
return res.status(401).json(headerIsNotValidMessage('Authorization'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const authenticateToken = (
|
const authenticateToken = (
|
||||||
req: any,
|
req: any,
|
||||||
res: any,
|
res: any,
|
||||||
next: any,
|
next: any,
|
||||||
key: string,
|
key: string,
|
||||||
tokenType: 'accessToken' | 'refreshToken' = 'accessToken'
|
tokenType: 'accessToken' | 'refreshToken'
|
||||||
) => {
|
) => {
|
||||||
const { MODE } = process.env
|
const { MODE } = process.env
|
||||||
if (MODE?.trim() !== 'server') {
|
if (MODE?.trim() !== 'server') {
|
||||||
|
|||||||
18
api/src/middlewares/desktop.ts
Normal file
18
api/src/middlewares/desktop.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export const desktopRestrict = (req: any, res: any, next: any) => {
|
||||||
|
const { MODE } = process.env
|
||||||
|
if (MODE?.trim() !== 'server')
|
||||||
|
return res.status(403).send('Not Allowed while in Desktop Mode.')
|
||||||
|
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
export const desktopUsername = (req: any, res: any, next: any) => {
|
||||||
|
const { MODE } = process.env
|
||||||
|
if (MODE?.trim() !== 'server')
|
||||||
|
return res.status(200).send({
|
||||||
|
userId: 12345,
|
||||||
|
username: 'DESKTOPusername',
|
||||||
|
displayName: 'DESKTOP User'
|
||||||
|
})
|
||||||
|
|
||||||
|
next()
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export const desktopRestrict = (req: any, res: any, next: any) => {
|
|
||||||
const { MODE } = process.env
|
|
||||||
if (MODE?.trim() !== 'server')
|
|
||||||
return res.status(403).send('Not Allowed while in Desktop Mode.')
|
|
||||||
|
|
||||||
next()
|
|
||||||
}
|
|
||||||
29
api/src/middlewares/header.ts
Normal file
29
api/src/middlewares/header.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Request, Response } from 'express'
|
||||||
|
|
||||||
|
export const verifyAcceptHeader = (req: Request, res: Response) => {
|
||||||
|
const acceptHeader = req.headers.accept
|
||||||
|
|
||||||
|
if (!acceptHeader) {
|
||||||
|
return res.status(406).json(headerIsNotPresentMessage('Accept'))
|
||||||
|
} else if (acceptHeader !== 'application/json') {
|
||||||
|
return res.status(406).json(headerIsNotValidMessage('Accept'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const verifyContentTypeHeader = (req: Request, res: Response) => {
|
||||||
|
const contentTypeHeader = req.headers['content-type']
|
||||||
|
|
||||||
|
if (!contentTypeHeader) {
|
||||||
|
return res.status(406).json(headerIsNotPresentMessage('Content-Type'))
|
||||||
|
} else if (contentTypeHeader !== 'application/json') {
|
||||||
|
return res.status(406).json(headerIsNotValidMessage('Content-Type'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const headerIsNotPresentMessage = (header: string) => ({
|
||||||
|
message: `${header} header is not present.`
|
||||||
|
})
|
||||||
|
|
||||||
|
export const headerIsNotValidMessage = (header: string) => ({
|
||||||
|
message: `${header} header is not valid.`
|
||||||
|
})
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
export * from './authenticateToken'
|
export * from './authenticateToken'
|
||||||
export * from './desktopRestrict'
|
export * from './desktop'
|
||||||
export * from './verifyAdmin'
|
export * from './verifyAdmin'
|
||||||
export * from './verifyAdminIfNeeded'
|
export * from './verifyAdminIfNeeded'
|
||||||
|
export * from './header'
|
||||||
|
export * from './mock'
|
||||||
|
|||||||
24
api/src/middlewares/mock.ts
Normal file
24
api/src/middlewares/mock.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
verifyAuthHeaderIsPresent,
|
||||||
|
verifyAcceptHeader,
|
||||||
|
verifyContentTypeHeader
|
||||||
|
} from './'
|
||||||
|
import { Request, Response, NextFunction } from 'express'
|
||||||
|
|
||||||
|
export const verifyHeaders = (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
switch (true) {
|
||||||
|
case verifyAuthHeaderIsPresent(req, res) !== undefined:
|
||||||
|
break
|
||||||
|
case verifyAcceptHeader(req, res) !== undefined:
|
||||||
|
break
|
||||||
|
case verifyContentTypeHeader(req, res) !== undefined:
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
}
|
||||||
80
api/src/model/Folder.ts
Normal file
80
api/src/model/Folder.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { Document, Schema, Model, model } from 'mongoose'
|
||||||
|
import {} from '@sasjs/utils'
|
||||||
|
|
||||||
|
export interface FolderPayload {
|
||||||
|
parentFolderUri: string
|
||||||
|
name: string
|
||||||
|
type: MemberType
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MemberType {
|
||||||
|
Folder = 'Folder',
|
||||||
|
File = 'File'
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMemberType = (value: string) => value in MemberType
|
||||||
|
|
||||||
|
export const getMemberType = (value: string) => {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFolderDocument extends FolderPayload, Document {
|
||||||
|
members: Schema.Types.ObjectId[]
|
||||||
|
type: MemberType
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFolder extends IFolderDocument {
|
||||||
|
addMember(memberId: Schema.Types.ObjectId): Promise<IFolder>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFolderModel extends Model<IFolder> {}
|
||||||
|
|
||||||
|
const folderSchema = new Schema({
|
||||||
|
name: { type: String, required: true },
|
||||||
|
parentFolderUri: { type: String, required: true },
|
||||||
|
members: [{ type: Schema.Types.ObjectId, refPath: 'member' }],
|
||||||
|
type: { type: String, required: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
folderSchema.post('save', (folder: IFolder, next: Function) => {
|
||||||
|
folder.populate('members', '').then(() => next())
|
||||||
|
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
|
||||||
|
// folderSchema.get('item', (folder: IFolder, next: Function) => {
|
||||||
|
|
||||||
|
// next()
|
||||||
|
// })
|
||||||
|
|
||||||
|
folderSchema.method(
|
||||||
|
'addMember',
|
||||||
|
async function (memberId: Schema.Types.ObjectId) {
|
||||||
|
const folderIdIndex = this.members.indexOf(memberId)
|
||||||
|
|
||||||
|
if (folderIdIndex === -1) this.members.push(memberId)
|
||||||
|
|
||||||
|
this.markModified('folders')
|
||||||
|
|
||||||
|
return this.save()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
folderSchema.method('getItem', async function (path: string) {
|
||||||
|
console.log(`🤖[getItem]🤖`)
|
||||||
|
console.log(`🤖[path]🤖`, path)
|
||||||
|
// const folderIdIndex = this.members.indexOf(memberId)
|
||||||
|
|
||||||
|
// if (folderIdIndex === -1) this.members.push(memberId)
|
||||||
|
|
||||||
|
// this.markModified('folders')
|
||||||
|
|
||||||
|
// return this.save()
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Folder: IFolderModel = model<IFolder, IFolderModel>(
|
||||||
|
'Folder',
|
||||||
|
folderSchema
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Folder
|
||||||
@@ -8,11 +8,7 @@ import {
|
|||||||
authenticateRefreshToken
|
authenticateRefreshToken
|
||||||
} from '../../middlewares'
|
} from '../../middlewares'
|
||||||
|
|
||||||
import {
|
import { authorizeValidation, tokenValidation } from '../../utils'
|
||||||
authorizeValidation,
|
|
||||||
getDesktopFields,
|
|
||||||
tokenValidation
|
|
||||||
} from '../../utils'
|
|
||||||
import { InfoJWT } from '../../types'
|
import { InfoJWT } from '../../types'
|
||||||
|
|
||||||
const authRouter = express.Router()
|
const authRouter = express.Router()
|
||||||
|
|||||||
67
api/src/routes/api/folders.ts
Normal file
67
api/src/routes/api/folders.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import { verifyHeaders } from '../../middlewares'
|
||||||
|
import { verifyQuery, setHeaders } from '../../utils'
|
||||||
|
import { FolderController } from '../../controllers'
|
||||||
|
|
||||||
|
const foldersRouter = express.Router()
|
||||||
|
const controller = new FolderController()
|
||||||
|
|
||||||
|
// https://sas.analytium.co.uk/folders/folders?parentFolderUri=/folders/folders/9e442a90-2c5b-40bb-982a-5fe3ff8a66b7
|
||||||
|
foldersRouter.post('/folders', verifyHeaders, async (req, res) => {
|
||||||
|
console.log(`🤖[req.query]🤖`, req.query)
|
||||||
|
console.log(`🤖[req.body]🤖`, req.body)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await controller.createFolder({
|
||||||
|
...req.query,
|
||||||
|
...req.body
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`🤖[response]🤖`, response)
|
||||||
|
|
||||||
|
res.send(response)
|
||||||
|
} catch (err: any) {
|
||||||
|
console.log(`🤖[error]🤖`, err)
|
||||||
|
|
||||||
|
res.status(403).send(err.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
foldersRouter.get('/folders/@item', verifyHeaders, async (req, res) => {
|
||||||
|
const queryParam = 'path'
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await controller.getItem({
|
||||||
|
...req.query,
|
||||||
|
...req.body
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`🤖[response]🤖`, response)
|
||||||
|
|
||||||
|
res.send(response)
|
||||||
|
} catch (err: any) {
|
||||||
|
console.log(`🤖[error]🤖`, err)
|
||||||
|
|
||||||
|
res.status(403).send(err.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (verifyQuery(req, res, [queryParam])) {
|
||||||
|
// const folderExist = Math.random() > 0.5
|
||||||
|
|
||||||
|
// setHeaders(res, folderExist)
|
||||||
|
|
||||||
|
// if (folderExist) {
|
||||||
|
// res.status(200).json({ message: 'Folder exists!' })
|
||||||
|
// } else {
|
||||||
|
// res.status(404).json({
|
||||||
|
// errorCode: 11512,
|
||||||
|
// message: 'No folders match the search criteria.',
|
||||||
|
// details: [`${queryParam}: ${req.query[queryParam]}`],
|
||||||
|
// links: [],
|
||||||
|
// version: 2
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
|
||||||
|
export default foldersRouter
|
||||||
@@ -5,6 +5,7 @@ import swaggerUi from 'swagger-ui-express'
|
|||||||
import {
|
import {
|
||||||
authenticateAccessToken,
|
authenticateAccessToken,
|
||||||
desktopRestrict,
|
desktopRestrict,
|
||||||
|
desktopUsername,
|
||||||
verifyAdmin
|
verifyAdmin
|
||||||
} from '../../middlewares'
|
} from '../../middlewares'
|
||||||
|
|
||||||
@@ -14,9 +15,12 @@ import userRouter from './user'
|
|||||||
import groupRouter from './group'
|
import groupRouter from './group'
|
||||||
import clientRouter from './client'
|
import clientRouter from './client'
|
||||||
import authRouter from './auth'
|
import authRouter from './auth'
|
||||||
|
import sessionRouter from './session'
|
||||||
|
import foldersRouter from './folders'
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
|
router.use('/session', desktopUsername, authenticateAccessToken, sessionRouter)
|
||||||
router.use('/auth', desktopRestrict, authRouter)
|
router.use('/auth', desktopRestrict, authRouter)
|
||||||
router.use(
|
router.use(
|
||||||
'/client',
|
'/client',
|
||||||
@@ -29,6 +33,7 @@ router.use('/drive', authenticateAccessToken, driveRouter)
|
|||||||
router.use('/group', desktopRestrict, groupRouter)
|
router.use('/group', desktopRestrict, groupRouter)
|
||||||
router.use('/stp', authenticateAccessToken, stpRouter)
|
router.use('/stp', authenticateAccessToken, stpRouter)
|
||||||
router.use('/user', desktopRestrict, userRouter)
|
router.use('/user', desktopRestrict, userRouter)
|
||||||
|
router.use('/folders', foldersRouter)
|
||||||
router.use(
|
router.use(
|
||||||
'/',
|
'/',
|
||||||
swaggerUi.serve,
|
swaggerUi.serve,
|
||||||
|
|||||||
17
api/src/routes/api/session.ts
Normal file
17
api/src/routes/api/session.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import { SessionController } from '../../controllers'
|
||||||
|
import { authenticateAccessToken } from '../../middlewares'
|
||||||
|
|
||||||
|
const sessionRouter = express.Router()
|
||||||
|
|
||||||
|
sessionRouter.get('/', async (req, res) => {
|
||||||
|
const controller = new SessionController()
|
||||||
|
try {
|
||||||
|
const response = await controller.session(req)
|
||||||
|
res.send(response)
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(403).send(err.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default sessionRouter
|
||||||
@@ -18,13 +18,13 @@ export const connectDB = async () => {
|
|||||||
process.driveLoc = driveLoc
|
process.driveLoc = driveLoc
|
||||||
|
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
const { SAS_PATH } = process.env
|
|
||||||
const sasDir = SAS_PATH ?? configuration.sasPath
|
|
||||||
|
|
||||||
process.sasLoc = path.join(sasDir, 'sas')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { SAS_PATH } = process.env
|
||||||
|
const sasDir = SAS_PATH ?? configuration.sasPath
|
||||||
|
|
||||||
|
process.sasLoc = path.join(sasDir, 'sas')
|
||||||
|
|
||||||
console.log('sasLoc: ', process.sasLoc)
|
console.log('sasLoc: ', process.sasLoc)
|
||||||
|
|
||||||
mongoose.connect(process.env.DB_CONNECT as string, async (err) => {
|
mongoose.connect(process.env.DB_CONNECT as string, async (err) => {
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ export * from './sleep'
|
|||||||
export * from './upload'
|
export * from './upload'
|
||||||
export * from './validation'
|
export * from './validation'
|
||||||
export * from './verifyTokenInDB'
|
export * from './verifyTokenInDB'
|
||||||
|
export * from './mock'
|
||||||
|
|||||||
38
api/src/utils/mock/header.ts
Normal file
38
api/src/utils/mock/header.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { Response } from 'express'
|
||||||
|
import { uuidv4 } from '@sasjs/utils'
|
||||||
|
|
||||||
|
export const setHeaders = (res: Response, isSuccess: boolean) => {
|
||||||
|
res.setHeader(
|
||||||
|
'cache-control',
|
||||||
|
`no-cache, no-store, max-age=0, must-revalidate`
|
||||||
|
)
|
||||||
|
res.setHeader(
|
||||||
|
'content-security-policy',
|
||||||
|
`default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' *.sas.com blob: data:; style-src 'self' 'unsafe-inline'; child-src 'self' blob: data: mailto:;`
|
||||||
|
)
|
||||||
|
res.setHeader(
|
||||||
|
'content-type',
|
||||||
|
`application/vnd.sas.${isSuccess ? 'content.folder' : 'error'}+json${
|
||||||
|
isSuccess ? '' : '; version=2;charset=UTF-8'
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
res.setHeader('pragma', `no-cache`)
|
||||||
|
res.setHeader('server', `Apache/2.4`)
|
||||||
|
res.setHeader('strict-transport-security', `max-age=31536000`)
|
||||||
|
res.setHeader('Transfer-Encoding', `chunked`)
|
||||||
|
res.setHeader('vary', `User-Agent`)
|
||||||
|
res.setHeader('x-content-type-options', `nosniff`)
|
||||||
|
res.setHeader('x-frame-options', `SAMEORIGIN`)
|
||||||
|
res.setHeader('x-xss-protection', `1; mode=block`)
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
const uuid = uuidv4()
|
||||||
|
|
||||||
|
res.setHeader('content-location', `/folders/folders/${uuid}`)
|
||||||
|
res.setHeader('etag', `-2066812946`)
|
||||||
|
res.setHeader('last-modified', `${new Date(Date.now()).toUTCString()}`)
|
||||||
|
res.setHeader('location', `/folders/folders/${uuid}`)
|
||||||
|
} else {
|
||||||
|
res.setHeader('sas-service-response-flag', `true`)
|
||||||
|
}
|
||||||
|
}
|
||||||
2
api/src/utils/mock/index.ts
Normal file
2
api/src/utils/mock/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './query'
|
||||||
|
export * from './header'
|
||||||
18
api/src/utils/mock/query.ts
Normal file
18
api/src/utils/mock/query.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Request, Response } from 'express'
|
||||||
|
|
||||||
|
export const verifyQuery = (req: Request, res: Response, args: string[]) => {
|
||||||
|
let isValid = true
|
||||||
|
const { query } = req
|
||||||
|
|
||||||
|
args.forEach((arg: string) => {
|
||||||
|
if (!Object.keys(query).includes(arg)) {
|
||||||
|
res.status(400).json({ message: `${arg} query argument is not present.` })
|
||||||
|
isValid = false
|
||||||
|
} else if (!query[arg]) {
|
||||||
|
res.status(400).json({ message: `${arg} query argument is not valid.` })
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return isValid
|
||||||
|
}
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "Session",
|
||||||
|
"description": "Get Session information"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "User",
|
"name": "User",
|
||||||
"description": "Operations about users"
|
"description": "Operations about users"
|
||||||
|
|||||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.9",
|
"version": "0.0.10",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.9",
|
"version": "0.0.10",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
"standard-version": "^9.3.2"
|
"standard-version": "^9.3.2"
|
||||||
@@ -1350,9 +1350,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimist": {
|
"node_modules/minimist": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/minimist-options": {
|
"node_modules/minimist-options": {
|
||||||
@@ -3158,9 +3158,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"minimist-options": {
|
"minimist-options": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.9",
|
"version": "0.0.10",
|
||||||
"description": "NodeJS wrapper for calling the SAS binary executable",
|
"description": "NodeJS wrapper for calling the SAS binary executable",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"server": "npm run server:prepare && npm run server:start",
|
"server": "npm run server:prepare && npm run server:start",
|
||||||
|
|||||||
Reference in New Issue
Block a user