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:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
22
restClient/auth.rest
Normal 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
45
restClient/drive.rest
Normal 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",
|
||||||
@@ -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
1
restClient/sample.sas
Normal file
@@ -0,0 +1 @@
|
|||||||
|
some code of sas
|
||||||
2
restClient/session.rest
Normal file
2
restClient/session.rest
Normal 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
10
restClient/users.rest
Normal 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"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user