1
0
mirror of https://github.com/sasjs/server.git synced 2026-01-11 00:10:06 +00:00

chore: drive auto-generated swagger

This commit is contained in:
Saad Jutt
2021-11-06 00:36:51 +05:00
parent dbbdaa83f0
commit 14c2def459
9 changed files with 648 additions and 45 deletions

1
.gitignore vendored
View File

@@ -6,6 +6,5 @@ node_modules/
sas/ sas/
tmp/ tmp/
build/ build/
public/
certificates/ certificates/
.env .env

553
public/swagger.yaml Normal file
View File

@@ -0,0 +1,553 @@
components:
examples: {}
headers: {}
parameters: {}
requestBodies: {}
responses: {}
schemas:
MemberType.folder:
enum:
- folder
type: string
FolderMember:
properties:
name:
type: string
type:
$ref: '#/components/schemas/MemberType.folder'
members:
items:
anyOf:
-
$ref: '#/components/schemas/FolderMember'
-
$ref: '#/components/schemas/ServiceMember'
type: array
required:
- name
- type
- members
type: object
additionalProperties: false
MemberType.service:
enum:
- service
type: string
ServiceMember:
properties:
name:
type: string
type:
$ref: '#/components/schemas/MemberType.service'
code:
type: string
required:
- name
- type
- code
type: object
additionalProperties: false
FileTree:
properties:
members:
items:
anyOf:
-
$ref: '#/components/schemas/FolderMember'
-
$ref: '#/components/schemas/ServiceMember'
type: array
required:
- members
type: object
additionalProperties: false
DeployResponse:
properties:
status:
type: string
message:
type: string
example:
$ref: '#/components/schemas/FileTree'
required:
- status
- message
type: object
additionalProperties: false
DeployPayload:
properties:
appLoc:
type: string
fileTree:
$ref: '#/components/schemas/FileTree'
required:
- fileTree
type: object
additionalProperties: false
UserResponse:
properties:
id:
type: number
format: double
username:
type: string
displayName:
type: string
required:
- id
- username
- displayName
type: object
additionalProperties: false
UserDetailsResponse:
properties:
id:
type: number
format: double
displayName:
type: string
username:
type: string
isActive:
type: boolean
isAdmin:
type: boolean
required:
- id
- displayName
- username
- isActive
- isAdmin
type: object
additionalProperties: false
UserPayload:
properties:
displayName:
type: string
description: 'Display name for user'
example: 'John Snow'
username:
type: string
description: 'Username for user'
example: johnSnow01
password:
type: string
description: 'Password for user'
isAdmin:
type: boolean
description: 'Account should be admin or not, defaults to false'
example: 'false'
isActive:
type: boolean
description: 'Account should be active or not, defaults to true'
example: 'true'
required:
- displayName
- username
- password
type: object
additionalProperties: false
ClientPayload:
properties:
clientId:
type: string
description: 'Client ID'
example: someFormattedClientID1234
clientSecret:
type: string
description: 'Client Secret'
example: someRandomCryptoString
required:
- clientId
- clientSecret
type: object
additionalProperties: false
AuthorizeResponse:
properties:
code:
type: string
description: 'Authorization code'
example: someRandomCryptoString
required:
- code
type: object
additionalProperties: false
AuthorizePayload:
properties:
username:
type: string
description: 'Username for user'
example: johnSnow01
password:
type: string
description: 'Password for user'
example: secretpassword
clientId:
type: string
description: 'Client ID'
example: someFormattedClientID1234
required:
- username
- password
- clientId
type: object
additionalProperties: false
TokenResponse:
properties:
accessToken:
type: string
description: 'Access Token'
example: someRandomCryptoString
refreshToken:
type: string
description: 'Refresh Token'
example: someRandomCryptoString
required:
- accessToken
- refreshToken
type: object
additionalProperties: false
TokenPayload:
properties:
clientId:
type: string
description: 'Client ID'
example: someFormattedClientID1234
code:
type: string
description: 'Authorization code'
example: someRandomCryptoString
required:
- clientId
- code
type: object
additionalProperties: false
InfoJWT:
properties:
clientId:
type: string
userId:
type: number
format: double
required:
- clientId
- userId
type: object
additionalProperties: false
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
info:
title: server
version: 0.0.1
description: 'SASjs server'
contact:
name: 'Analytium Ltd'
openapi: 3.0.0
paths:
/SASjsApi/drive/deploy:
post:
operationId: Deploy
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: '#/components/schemas/DeployResponse'
examples:
'Example 1':
value: {status: success, message: 'Files deployed successfully to @sasjs/server.'}
'400':
description: 'Invalid Format'
content:
application/json:
schema:
$ref: '#/components/schemas/DeployResponse'
examples:
'Example 1':
value: {status: failure, message: 'Provided not supported data format.'}
'500':
description: 'Execution Error'
content:
application/json:
schema:
$ref: '#/components/schemas/DeployResponse'
examples:
'Example 1':
value: {status: failure, message: 'Deployment failed!'}
description: 'Creates/updates files within SASjs Drive using provided payload.'
tags:
- Drive
security:
-
bearerAuth: []
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DeployPayload'
/SASjsApi/user:
get:
operationId: GetAllUsers
responses:
'200':
description: Ok
content:
application/json:
schema:
items:
$ref: '#/components/schemas/UserResponse'
type: array
examples:
'Example 1':
value: [{id: 123, username: johnusername, displayName: John}, {id: 456, username: starkusername, displayName: Stark}]
description: 'Get list of all users (username, displayname). All users can request this.'
tags:
- User
security:
-
bearerAuth: []
parameters: []
post:
operationId: CreateUser
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: '#/components/schemas/UserDetailsResponse'
examples:
'Example 1':
value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
description: 'Create user with the following attributes: UserId, UserName, Password, isAdmin, isActive. Admin only task.'
tags:
- User
security:
-
bearerAuth: []
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserPayload'
'/SASjsApi/user/{userId}':
get:
operationId: GetUser
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: '#/components/schemas/UserDetailsResponse'
description: 'Get user properties - such as group memberships, userName, displayName.'
tags:
- User
security:
-
bearerAuth: []
parameters:
-
description: 'The user''s identifier'
in: path
name: userId
required: true
schema:
format: double
type: number
example: 1234
patch:
operationId: UpdateUser
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: '#/components/schemas/UserDetailsResponse'
examples:
'Example 1':
value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
description: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.'
tags:
- User
security:
-
bearerAuth: []
parameters:
-
description: 'The user''s identifier'
in: path
name: userId
required: true
schema:
format: double
type: number
example: '1234'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserPayload'
delete:
operationId: DeleteUser
responses:
'204':
description: 'No content'
description: 'Delete a user. Can be performed either by admins, or the user in question.'
tags:
- User
security:
-
bearerAuth: []
parameters:
-
description: 'The user''s identifier'
in: path
name: userId
required: true
schema:
format: double
type: number
example: 1234
requestBody:
required: true
content:
application/json:
schema:
properties:
password:
type: string
type: object
/SASjsApi/client:
post:
operationId: CreateClient
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: '#/components/schemas/ClientPayload'
examples:
'Example 1':
value: {clientId: someFormattedClientID1234, clientSecret: someRandomCryptoString}
description: 'Create client with the following attributes: ClientId, ClientSecret. Admin only task.'
tags:
- Client
security:
-
bearerAuth: []
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ClientPayload'
/SASjsApi/auth/authorize:
post:
operationId: Authorize
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: '#/components/schemas/AuthorizeResponse'
examples:
'Example 1':
value: {code: someRandomCryptoString}
description: 'Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE'
tags:
- Auth
security: []
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AuthorizePayload'
/SASjsApi/auth/token:
post:
operationId: Token
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: '#/components/schemas/TokenResponse'
examples:
'Example 1':
value: {accessToken: someRandomCryptoString, refreshToken: someRandomCryptoString}
description: 'Accepts client/auth code and returns access/refresh tokens'
tags:
- Auth
security: []
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TokenPayload'
/SASjsApi/auth/refresh:
post:
operationId: Refresh
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: '#/components/schemas/TokenResponse'
examples:
'Example 1':
value: {accessToken: someRandomCryptoString, refreshToken: someRandomCryptoString}
description: 'Returns new access/refresh tokens'
tags:
- Auth
security:
-
bearerAuth: []
parameters: []
/SASjsApi/auth/logout:
post:
operationId: Logout
responses:
'204':
description: 'No content'
description: 'Logout terminate access/refresh tokens and returns nothing'
tags:
- Auth
security:
-
bearerAuth: []
parameters: []
servers:
-
url: /
tags:
-
name: User
description: 'Operations about users'
-
name: Client
description: 'Operations about clients'
-
name: Auth
description: 'Operations about auth'
-
name: Drive
description: 'Operations about drive'

View File

@@ -1,11 +1,11 @@
import { MemberType, FolderMember, ServiceMember } from '../types' import { MemberType, FolderMember, ServiceMember, FileTree } from '../types'
import { getTmpFilesFolderPath } from '../utils/file' import { getTmpFilesFolderPath } from '../utils/file'
import { createFolder, createFile, asyncForEach } from '@sasjs/utils' import { createFolder, createFile, asyncForEach } from '@sasjs/utils'
import path from 'path' import path from 'path'
// REFACTOR: export FileTreeCpntroller // REFACTOR: export FileTreeCpntroller
export const createFileTree = async ( export const createFileTree = async (
members: [FolderMember, ServiceMember], members: (FolderMember | ServiceMember)[],
parentFolders: string[] = [] parentFolders: string[] = []
) => { ) => {
const destinationPath = path.join( const destinationPath = path.join(
@@ -16,7 +16,7 @@ export const createFileTree = async (
await asyncForEach(members, async (member: FolderMember | ServiceMember) => { await asyncForEach(members, async (member: FolderMember | ServiceMember) => {
let name = member.name let name = member.name
if (member.type === 'service') name += '.sas' if (member.type === MemberType.service) name += '.sas'
if (member.type === MemberType.folder) { if (member.type === MemberType.folder) {
await createFolder(path.join(destinationPath, name)).catch((err) => await createFolder(path.join(destinationPath, name)).catch((err) =>
@@ -36,19 +36,19 @@ export const createFileTree = async (
return Promise.resolve() return Promise.resolve()
} }
export const getTreeExample = () => ({ export const getTreeExample = (): FileTree => ({
members: [ members: [
{ {
name: 'jobs', name: 'jobs',
type: 'folder', type: MemberType.folder,
members: [ members: [
{ {
name: 'extract', name: 'extract',
type: 'folder', type: MemberType.folder,
members: [ members: [
{ {
name: 'makedata1', name: 'makedata1',
type: 'service', type: MemberType.service,
code: '%put Hello World!;' code: '%put Hello World!;'
} }
] ]

63
src/controllers/drive.ts Normal file
View File

@@ -0,0 +1,63 @@
import { Security, Route, Tags, Example, Post, Body, Response } from 'tsoa'
import { createFileTree, getTreeExample } from '.'
import { FileTree, isFileTree } from '../types'
interface DeployPayload {
appLoc?: string
fileTree: FileTree
}
interface DeployResponse {
status: string
message: string
example?: FileTree
}
const fileTreeExample = getTreeExample()
const successResponse: DeployResponse = {
status: 'success',
message: 'Files deployed successfully to @sasjs/server.'
}
const invalidFormatResponse: DeployResponse = {
status: 'failure',
message: 'Provided not supported data format.',
example: fileTreeExample
}
const execErrorResponse: DeployResponse = {
status: 'failure',
message: 'Deployment failed!'
}
@Security('bearerAuth')
@Route('SASjsApi/drive')
@Tags('Drive')
export default class DriveController {
/**
* Creates/updates files within SASjs Drive using provided payload.
*
*/
@Example<DeployResponse>(successResponse)
@Response<DeployResponse>(400, 'Invalid Format', invalidFormatResponse)
@Response<DeployResponse>(500, 'Execution Error', execErrorResponse)
@Post('/deploy')
public async deploy(@Body() body: DeployPayload): Promise<DeployResponse> {
return deploy(body)
}
}
const deploy = async (data: DeployPayload) => {
if (!isFileTree(data.fileTree)) {
throw { code: 400, ...invalidFormatResponse }
}
await createFileTree(
data.fileTree.members,
data.appLoc ? data.appLoc.replace(/^\//, '').split('/') : []
).catch((err) => {
throw { code: 500, ...execErrorResponse, ...err }
})
return successResponse
}

View File

@@ -1,35 +1,18 @@
import express from 'express' import express from 'express'
import { createFileTree, getTreeExample } from '../../controllers' import DriveController from '../../controllers/drive'
import { isFileTree } from '../../types'
const driveRouter = express.Router() const driveRouter = express.Router()
driveRouter.post('/deploy', async (req, res) => { driveRouter.post('/deploy', async (req, res) => {
if (!isFileTree(req.body.fileTree)) { const controller = new DriveController()
res.status(400).send({ try {
status: 'failure', const response = await controller.deploy(req.body)
message: 'Provided not supported data format.', res.send(response)
example: getTreeExample() } catch (err: any) {
}) const statusCode = err.code
delete err.code
return res.status(statusCode).send(err)
} }
await createFileTree(
req.body.fileTree.members,
req.body.appLoc ? req.body.appLoc.replace(/^\//, '').split('/') : []
)
.then(() => {
res.status(200).send({
status: 'success',
message: 'Files deployed successfully to @sasjs/server.'
})
})
.catch((err) => {
res
.status(500)
.send({ status: 'failure', message: 'Deployment failed!', ...err })
})
}) })
export default driveRouter export default driveRouter

View File

@@ -25,7 +25,7 @@ router.use(
swaggerUi.serve, swaggerUi.serve,
swaggerUi.setup(undefined, { swaggerUi.setup(undefined, {
swaggerOptions: { swaggerOptions: {
url: '/swagger.json' url: '/swagger.yaml'
} }
}) })
) )

View File

@@ -9,6 +9,7 @@ import { folderExists, fileExists, readFile, deleteFolder } from '@sasjs/utils'
import path from 'path' import path from 'path'
import { generateAccessToken } from '../../../controllers/auth' import { generateAccessToken } from '../../../controllers/auth'
import { saveTokensInDB } from '../../../utils' import { saveTokensInDB } from '../../../utils'
import { FolderMember, ServiceMember } from '../../../types'
const clientId = 'someclientID' const clientId = 'someclientID'
const user = { const user = {
@@ -135,21 +136,20 @@ describe('files', () => {
) )
await expect(folderExists(testJobFolder)).resolves.toEqual(true) await expect(folderExists(testJobFolder)).resolves.toEqual(true)
const testJobFile = const exampleService = getExampleService()
path.join( const testJobFile = path.join(testJobFolder, exampleService.name) + '.sas'
testJobFolder,
getTreeExample().members[0].members[0].members[0].name
) + '.sas'
console.log(`[testJobFile]`, testJobFile) console.log(`[testJobFile]`, testJobFile)
await expect(fileExists(testJobFile)).resolves.toEqual(true) await expect(fileExists(testJobFile)).resolves.toEqual(true)
await expect(readFile(testJobFile)).resolves.toEqual( await expect(readFile(testJobFile)).resolves.toEqual(exampleService.code)
getTreeExample().members[0].members[0].members[0].code
)
await deleteFolder(getTmpFilesFolderPath()) await deleteFolder(getTmpFilesFolderPath())
}) })
}) })
}) })
const getExampleService = (): ServiceMember =>
((getTreeExample().members[0] as FolderMember).members[0] as FolderMember)
.members[0] as ServiceMember

View File

@@ -1,5 +1,5 @@
export interface FileTree { export interface FileTree {
members: [FolderMember, ServiceMember] members: (FolderMember | ServiceMember)[]
} }
export enum MemberType { export enum MemberType {
@@ -10,7 +10,7 @@ export enum MemberType {
export interface FolderMember { export interface FolderMember {
name: string name: string
type: MemberType.folder type: MemberType.folder
members: [FolderMember, ServiceMember] members: (FolderMember | ServiceMember)[]
} }
export interface ServiceMember { export interface ServiceMember {

View File

@@ -22,8 +22,13 @@
{ {
"name": "Auth", "name": "Auth",
"description": "Operations about auth" "description": "Operations about auth"
},
{
"name": "Drive",
"description": "Operations about drive"
} }
], ],
"yaml": true,
"specVersion": 3 "specVersion": 3
} }
} }