1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 11:24:35 +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/
tmp/
build/
public/
certificates/
.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 { createFolder, createFile, asyncForEach } from '@sasjs/utils'
import path from 'path'
// REFACTOR: export FileTreeCpntroller
export const createFileTree = async (
members: [FolderMember, ServiceMember],
members: (FolderMember | ServiceMember)[],
parentFolders: string[] = []
) => {
const destinationPath = path.join(
@@ -16,7 +16,7 @@ export const createFileTree = async (
await asyncForEach(members, async (member: FolderMember | ServiceMember) => {
let name = member.name
if (member.type === 'service') name += '.sas'
if (member.type === MemberType.service) name += '.sas'
if (member.type === MemberType.folder) {
await createFolder(path.join(destinationPath, name)).catch((err) =>
@@ -36,19 +36,19 @@ export const createFileTree = async (
return Promise.resolve()
}
export const getTreeExample = () => ({
export const getTreeExample = (): FileTree => ({
members: [
{
name: 'jobs',
type: 'folder',
type: MemberType.folder,
members: [
{
name: 'extract',
type: 'folder',
type: MemberType.folder,
members: [
{
name: 'makedata1',
type: 'service',
type: MemberType.service,
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 { createFileTree, getTreeExample } from '../../controllers'
import { isFileTree } from '../../types'
import DriveController from '../../controllers/drive'
const driveRouter = express.Router()
driveRouter.post('/deploy', async (req, res) => {
if (!isFileTree(req.body.fileTree)) {
res.status(400).send({
status: 'failure',
message: 'Provided not supported data format.',
example: getTreeExample()
})
return
const controller = new DriveController()
try {
const response = await controller.deploy(req.body)
res.send(response)
} catch (err: any) {
const statusCode = err.code
delete err.code
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

View File

@@ -25,7 +25,7 @@ router.use(
swaggerUi.serve,
swaggerUi.setup(undefined, {
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 { generateAccessToken } from '../../../controllers/auth'
import { saveTokensInDB } from '../../../utils'
import { FolderMember, ServiceMember } from '../../../types'
const clientId = 'someclientID'
const user = {
@@ -135,21 +136,20 @@ describe('files', () => {
)
await expect(folderExists(testJobFolder)).resolves.toEqual(true)
const testJobFile =
path.join(
testJobFolder,
getTreeExample().members[0].members[0].members[0].name
) + '.sas'
const exampleService = getExampleService()
const testJobFile = path.join(testJobFolder, exampleService.name) + '.sas'
console.log(`[testJobFile]`, testJobFile)
await expect(fileExists(testJobFile)).resolves.toEqual(true)
await expect(readFile(testJobFile)).resolves.toEqual(
getTreeExample().members[0].members[0].members[0].code
)
await expect(readFile(testJobFile)).resolves.toEqual(exampleService.code)
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 {
members: [FolderMember, ServiceMember]
members: (FolderMember | ServiceMember)[]
}
export enum MemberType {
@@ -10,7 +10,7 @@ export enum MemberType {
export interface FolderMember {
name: string
type: MemberType.folder
members: [FolderMember, ServiceMember]
members: (FolderMember | ServiceMember)[]
}
export interface ServiceMember {

View File

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