1
0
mirror of https://github.com/sasjs/server.git synced 2026-01-14 17:30:05 +00:00

Merge pull request #75 from sasjs/improve-file-upload

Improve file upload
This commit is contained in:
Muhammad Saad
2022-03-02 18:56:28 +05:00
committed by GitHub
11 changed files with 181 additions and 14 deletions

View File

@@ -663,13 +663,22 @@ paths:
examples: examples:
'Example 1': 'Example 1':
value: {status: failure, message: 'File request failed.'} value: {status: failure, message: 'File request failed.'}
description: "It's optional to either provide `_filePath` in url as query parameter\nOr provide `filePath` in body as form field.\nBut it's required to provided else API will respond with Bad Request."
summary: 'Create a file in SASjs Drive' summary: 'Create a file in SASjs Drive'
tags: tags:
- Drive - Drive
security: security:
- -
bearerAuth: [] bearerAuth: []
parameters: [] parameters:
-
description: 'Location of SAS program'
in: query
name: _filePath
required: false
schema:
type: string
example: /Public/somefolder/some.file.sas
requestBody: requestBody:
required: true required: true
content: content:
@@ -677,13 +686,12 @@ paths:
schema: schema:
type: object type: object
properties: properties:
filePath:
type: string
file: file:
type: string type: string
format: binary format: binary
filePath:
type: string
required: required:
- filePath
- file - file
patch: patch:
operationId: UpdateFile operationId: UpdateFile

View File

@@ -117,7 +117,13 @@ export class DriveController {
} }
/** /**
* It's optional to either provide `_filePath` in url as query parameter
* Or provide `filePath` in body as form field.
* But it's required to provided else API will respond with Bad Request.
*
* @summary Create a file in SASjs Drive * @summary Create a file in SASjs Drive
* @param _filePath Location of SAS program
* @example _filePath "/Public/somefolder/some.file.sas"
* *
*/ */
@Example<UpdateFileResponse>({ @Example<UpdateFileResponse>({
@@ -129,10 +135,11 @@ export class DriveController {
}) })
@Post('/file') @Post('/file')
public async saveFile( public async saveFile(
@FormField() filePath: string, @UploadedFile() file: Express.Multer.File,
@UploadedFile() file: Express.Multer.File @Query() _filePath?: string,
@FormField() filePath?: string
): Promise<UpdateFileResponse> { ): Promise<UpdateFileResponse> {
return saveFile(filePath, file) return saveFile((_filePath ?? filePath)!, file)
} }
/** /**

View File

@@ -3,7 +3,12 @@ import { deleteFile } from '@sasjs/utils'
import { multerSingle } from '../../middlewares/multer' import { multerSingle } from '../../middlewares/multer'
import { DriveController } from '../../controllers/' import { DriveController } from '../../controllers/'
import { getFileDriveValidation, updateFileDriveValidation } from '../../utils' import {
getFileDriveValidation,
updateFileDriveValidation,
uploadFileBodyValidation,
uploadFileParamValidation
} from '../../utils'
const controller = new DriveController() const controller = new DriveController()
@@ -42,16 +47,22 @@ driveRouter.post(
'/file', '/file',
(...arg) => multerSingle('file', arg), (...arg) => multerSingle('file', arg),
async (req, res) => { async (req, res) => {
const { error, value: body } = updateFileDriveValidation(req.body) const { error: errQ, value: query } = uploadFileParamValidation(req.query)
if (error) { const { error: errB, value: body } = uploadFileBodyValidation(req.body)
if (errQ && errB) {
if (req.file) await deleteFile(req.file.path) if (req.file) await deleteFile(req.file.path)
return res.status(400).send(error.details[0].message) return res.status(400).send(errB.details[0].message)
} }
if (!req.file) return res.status(400).send('"file" is not present.') if (!req.file) return res.status(400).send('"file" is not present.')
try { try {
const response = await controller.saveFile(body.filePath, req.file) const response = await controller.saveFile(
req.file,
query._filePath,
body.filePath
)
res.send(response) res.send(response)
} catch (err: any) { } catch (err: any) {
await deleteFile(req.file.path) await deleteFile(req.file.path)

View File

@@ -172,7 +172,7 @@ describe('files', () => {
describe('file', () => { describe('file', () => {
describe('create', () => { describe('create', () => {
it('should create a SAS file on drive', async () => { it('should create a SAS file on drive having filePath as form field', async () => {
const res = await request(app) const res = await request(app)
.post('/SASjsApi/drive/file') .post('/SASjsApi/drive/file')
.auth(accessToken, { type: 'bearer' }) .auth(accessToken, { type: 'bearer' })
@@ -185,6 +185,19 @@ describe('files', () => {
}) })
}) })
it('should create a SAS file on drive having _filePath as query param', async () => {
const res = await request(app)
.post('/SASjsApi/drive/file')
.auth(accessToken, { type: 'bearer' })
.query({ _filePath: '/my/path/code1.sas' })
.attach('file', path.join(__dirname, 'files', 'sample.sas'))
expect(res.statusCode).toEqual(200)
expect(res.body).toEqual({
status: 'success'
})
})
it('should respond with Unauthorized if access token is not present', async () => { it('should respond with Unauthorized if access token is not present', async () => {
const res = await request(app) const res = await request(app)
.post('/SASjsApi/drive/file') .post('/SASjsApi/drive/file')

View File

@@ -72,12 +72,25 @@ export const getFileDriveValidation = (data: any): Joi.ValidationResult =>
}).validate(data) }).validate(data)
export const updateFileDriveValidation = (data: any): Joi.ValidationResult => export const updateFileDriveValidation = (data: any): Joi.ValidationResult =>
Joi.object({
filePath: Joi.string().required(),
fileContent: Joi.string().required()
}).validate(data)
export const uploadFileBodyValidation = (data: any): Joi.ValidationResult =>
Joi.object({ Joi.object({
filePath: Joi.string().pattern(/.sas$/).required().messages({ filePath: Joi.string().pattern(/.sas$/).required().messages({
'string.pattern.base': `Valid extensions for filePath: .sas` 'string.pattern.base': `Valid extensions for filePath: .sas`
}) })
}).validate(data) }).validate(data)
export const uploadFileParamValidation = (data: any): Joi.ValidationResult =>
Joi.object({
_filePath: Joi.string().pattern(/.sas$/).required().messages({
'string.pattern.base': `Valid extensions for filePath: .sas`
})
}).validate(data)
export const runSASValidation = (data: any): Joi.ValidationResult => export const runSASValidation = (data: any): Joi.ValidationResult =>
Joi.object({ Joi.object({
code: Joi.string().required() code: Joi.string().required()

22
restClient/auth.rest Normal file
View File

@@ -0,0 +1,22 @@
### Get Auth Code
POST http://localhost:5000/SASjsApi/auth/authorize
Content-Type: application/json
{
"username": "secretuser",
"password": "secretpassword",
"client_id": "clientID1"
}
### Exchange AuthCode with Access/Refresh Tokens
POST http://localhost:5000/SASjsApi/auth/token
Content-Type: application/json
{
"client_id": "clientID1",
"client_secret": "clientID1secret",
"code": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiJjbGllbnRJRDEiLCJ1c2VybmFtZSI6InVzZXJuYW1lMSIsImlzYWRtaW4iOmZhbHNlLCJpc2FjdGl2ZSI6dHJ1ZSwiaWF0IjoxNjM1ODA0MDYxLCJleHAiOjE2MzU4MDQwOTF9.jV7DpBWG7XAGODs22zAW_kWOqVLZvOxmmYJGpSNQ-KM"
}
### Perform logout to deactivate access token instantly
DELETE http://localhost:5000/SASjsApi/auth/logout

45
restClient/drive.rest Normal file
View File

@@ -0,0 +1,45 @@
###
POST http://localhost:5000/SASjsApi/drive/deploy
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiJjbGllbnRJRDEiLCJ1c2VybmFtZSI6InVzZXJuYW1lMSIsImlzYWRtaW4iOmZhbHNlLCJpc2FjdGl2ZSI6dHJ1ZSwiaWF0IjoxNjM1ODA0MDc2LCJleHAiOjE2MzU4OTA0NzZ9.Cx1F54ILgAUtnkit0Wg1K1YVO2RdNjOnTKdPhUtDm5I
### multipart upload to sas server file
POST http://localhost:5000/SASjsApi/drive/file
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="filePath"
/saad/files/new.sas
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="sample_new.sas"
Content-Type: application/octet-stream
< ./sample.sas
------WebKitFormBoundary7MA4YWxkTrZu0gW--
### multipart upload to sas server file text
POST http://localhost:5000/SASjsApi/drive/file
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW \n
Content-Disposition: form-data; name="filePath"
/saad/files/new2.sas
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="sample_new.sas"
Content-Type: text/plain
SOME CONTENTS OF SAS FILE IN REQUEST
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Users
"username": "username1",
"password": "some password",
"username": "username2",
"password": "some password",
Admins
"username": "secretuser",
"password": "secretpassword",

View File

@@ -23,7 +23,7 @@ Content-Type: application/json
"client_secret": "newClientSecret" "client_secret": "newClientSecret"
} }
### ###
POST https://sas.analytium.co.uk:5002/SASjsApi/auth/authorize POST http://localhost:5000/SASjsApi/auth/authorize
Content-Type: application/json Content-Type: application/json
{ {
@@ -45,6 +45,41 @@ Content-Type: application/json
### ###
DELETE http://localhost:5000/SASjsApi/auth/logout DELETE http://localhost:5000/SASjsApi/auth/logout
###
GET http://localhost:5000/SASjsApi/session
### multipart upload to sas server file
POST http://localhost:5000/SASjsApi/drive/file
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="filePath"
/saad/files/new.sas
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="sample_new.sas"
Content-Type: application/octet-stream
< ./sample.sas
------WebKitFormBoundary7MA4YWxkTrZu0gW--
### multipart upload to sas server file text
POST http://localhost:5000/SASjsApi/drive/file
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW \n
Content-Disposition: form-data; name="filePath"
/saad/files/new2.sas
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="sample_new.sas"
Content-Type: text/plain
SOME CONTENTS OF SAS FILE IN REQUEST
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Users Users
"username": "username1", "username": "username1",

1
restClient/sample.sas Normal file
View File

@@ -0,0 +1 @@
some code of sas

2
restClient/session.rest Normal file
View File

@@ -0,0 +1,2 @@
### Get current user's info via access token
GET http://localhost:5000/SASjsApi/session

10
restClient/users.rest Normal file
View File

@@ -0,0 +1,10 @@
### Create User
POST http://localhost:5000/SASjsApi/user
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiJjbGllbnRJRDEiLCJ1c2VybmFtZSI6InNlY3JldHVzZXIiLCJpc2FkbWluIjp0cnVlLCJpc2FjdGl2ZSI6dHJ1ZSwiaWF0IjoxNjM1ODAzOTc3LCJleHAiOjE2MzU4OTAzNzd9.f-FLgLwryKvB5XrihdzaGZajO3d5E5OHEEuJI_03GRI
Content-Type: application/json
{
"displayname": "User 2",
"username": "username2",
"password": "some password"
}