mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 19:34:34 +00:00
Compare commits
5 Commits
v0.5.0
...
mock-viya-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcef9a4a9d | ||
|
|
a4d5ee99c4 | ||
|
|
d7e835778b | ||
|
|
d7d3bb285f | ||
|
|
d532d74879 |
@@ -59,12 +59,12 @@ It will build following images if running first time:
|
||||
|
||||
### Using node:
|
||||
|
||||
#### Development (running api and web seperately):
|
||||
#### Development (running api and web separately):
|
||||
|
||||
##### 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.
|
||||
|
||||
```
|
||||
|
||||
@@ -8,6 +8,8 @@ import webRouter from './routes/web'
|
||||
import apiRouter from './routes/api'
|
||||
import { connectDB, getWebBuildFolderPath } from './utils'
|
||||
|
||||
import { FolderController } from './controllers'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const app = express()
|
||||
@@ -30,4 +32,8 @@ app.use(express.json({ limit: '50mb' }))
|
||||
|
||||
app.use(express.static(getWebBuildFolderPath()))
|
||||
|
||||
const folderController = new FolderController()
|
||||
|
||||
folderController.addRootFolder()
|
||||
|
||||
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 })
|
||||
@@ -5,3 +5,4 @@ export * from './group'
|
||||
export * from './stp'
|
||||
export * from './user'
|
||||
export * from './session'
|
||||
export * from './folder'
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { Request, Response } from 'express'
|
||||
import { verifyTokenInDB } from '../utils'
|
||||
import { headerIsNotPresentMessage, headerIsNotValidMessage } from './header'
|
||||
|
||||
export const authenticateAccessToken = (req: any, res: any, next: any) => {
|
||||
authenticateToken(
|
||||
@@ -21,6 +23,18 @@ 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 = (
|
||||
req: any,
|
||||
res: any,
|
||||
|
||||
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.`
|
||||
})
|
||||
@@ -2,3 +2,5 @@ export * from './authenticateToken'
|
||||
export * from './desktop'
|
||||
export * from './verifyAdmin'
|
||||
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
|
||||
} from '../../middlewares'
|
||||
|
||||
import {
|
||||
authorizeValidation,
|
||||
getDesktopFields,
|
||||
tokenValidation
|
||||
} from '../../utils'
|
||||
import { authorizeValidation, tokenValidation } from '../../utils'
|
||||
import { InfoJWT } from '../../types'
|
||||
|
||||
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
|
||||
@@ -16,6 +16,7 @@ import groupRouter from './group'
|
||||
import clientRouter from './client'
|
||||
import authRouter from './auth'
|
||||
import sessionRouter from './session'
|
||||
import foldersRouter from './folders'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
@@ -32,6 +33,7 @@ router.use('/drive', authenticateAccessToken, driveRouter)
|
||||
router.use('/group', desktopRestrict, groupRouter)
|
||||
router.use('/stp', authenticateAccessToken, stpRouter)
|
||||
router.use('/user', desktopRestrict, userRouter)
|
||||
router.use('/folders', foldersRouter)
|
||||
router.use(
|
||||
'/',
|
||||
swaggerUi.serve,
|
||||
|
||||
@@ -10,3 +10,4 @@ export * from './sleep'
|
||||
export * from './upload'
|
||||
export * from './validation'
|
||||
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
|
||||
}
|
||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1350,9 +1350,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/minimist-options": {
|
||||
@@ -3158,9 +3158,9 @@
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"minimist-options": {
|
||||
|
||||
Reference in New Issue
Block a user