diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index 9a104b8..b42c4b4 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -230,7 +230,7 @@ components: - fileTree type: object additionalProperties: false - UpdateFileResponse: + FileFolderResponse: properties: status: type: string @@ -240,6 +240,16 @@ components: - status type: object additionalProperties: false + AddFolderPayload: + properties: + folderPath: + type: string + description: 'Location of folder' + example: /Public/someFolder + required: + - folderPath + type: object + additionalProperties: false TreeNode: properties: name: @@ -839,7 +849,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateFileResponse' + $ref: '#/components/schemas/FileFolderResponse' examples: 'Example 1': value: {status: success} @@ -848,7 +858,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateFileResponse' + $ref: '#/components/schemas/FileFolderResponse' examples: 'Example 1': value: {status: failure, message: 'File request failed.'} @@ -861,7 +871,7 @@ paths: bearerAuth: [] parameters: - - description: 'Location of SAS program' + description: 'Location of file' in: query name: _filePath required: false @@ -890,7 +900,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateFileResponse' + $ref: '#/components/schemas/FileFolderResponse' examples: 'Example 1': value: {status: success} @@ -899,7 +909,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateFileResponse' + $ref: '#/components/schemas/FileFolderResponse' examples: 'Example 1': value: {status: failure, message: 'File request failed.'} @@ -990,6 +1000,40 @@ paths: schema: type: string example: /Public/somefolder/ + post: + operationId: AddFolder + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/FileFolderResponse' + examples: + 'Example 1': + value: {status: success} + '409': + description: 'Folder already exists' + content: + application/json: + schema: + $ref: '#/components/schemas/FileFolderResponse' + examples: + 'Example 1': + value: {status: failure, message: 'Add folder request failed.'} + summary: 'Create an empty folder in SASjs Drive' + tags: + - Drive + security: + - + bearerAuth: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AddFolderPayload' /SASjsApi/drive/filetree: get: operationId: GetFileTree diff --git a/api/src/controllers/drive.ts b/api/src/controllers/drive.ts index 358e276..d99a4d2 100644 --- a/api/src/controllers/drive.ts +++ b/api/src/controllers/drive.ts @@ -59,11 +59,19 @@ interface GetFileTreeResponse { tree: TreeNode } -interface UpdateFileResponse { +interface FileFolderResponse { status: string message?: string } +interface AddFolderPayload { + /** + * Location of folder + * @example "/Public/someFolder" + */ + folderPath: string +} + const fileTreeExample = getTreeExample() const successDeployResponse: DeployResponse = { @@ -141,6 +149,17 @@ export class DriveController { return getFolder(_folderPath) } + /** + * + * @summary Delete file from SASjs Drive + * @query _filePath Location of file + * @example _filePath "/Public/somefolder/some.file" + */ + @Delete('/file') + public async deleteFile(@Query() _filePath: string) { + return deleteFile(_filePath) + } + /** * * @summary Delete folder from SASjs Drive @@ -152,31 +171,20 @@ export class DriveController { return deleteFolder(_folderPath) } - /** - * - * @summary Delete file from SASjs Drive - * @query _filePath Location of SAS program - * @example _filePath "/Public/somefolder/some.file" - */ - @Delete('/file') - public async deleteFile(@Query() _filePath: string) { - return deleteFile(_filePath) - } - /** * 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 provide else API will respond with Bad Request. * * @summary Create a file in SASjs Drive - * @param _filePath Location of SAS program + * @param _filePath Location of file * @example _filePath "/Public/somefolder/some.file.sas" * */ - @Example({ + @Example({ status: 'success' }) - @Response(403, 'File already exists', { + @Response(403, 'File already exists', { status: 'failure', message: 'File request failed.' }) @@ -185,10 +193,28 @@ export class DriveController { @UploadedFile() file: Express.Multer.File, @Query() _filePath?: string, @FormField() filePath?: string - ): Promise { + ): Promise { return saveFile((_filePath ?? filePath)!, file) } + /** + * @summary Create an empty folder in SASjs Drive + * + */ + @Example({ + status: 'success' + }) + @Response(409, 'Folder already exists', { + status: 'failure', + message: 'Add folder request failed.' + }) + @Post('/folder') + public async addFolder( + @Body() body: AddFolderPayload + ): Promise { + return addFolder(body.folderPath) + } + /** * It's optional to either provide `_filePath` in url as query parameter * Or provide `filePath` in body as form field. @@ -199,10 +225,10 @@ export class DriveController { * @example _filePath "/Public/somefolder/some.file.sas" * */ - @Example({ + @Example({ status: 'success' }) - @Response(403, `File doesn't exist`, { + @Response(403, `File doesn't exist`, { status: 'failure', message: 'File request failed.' }) @@ -211,7 +237,7 @@ export class DriveController { @UploadedFile() file: Express.Multer.File, @Query() _filePath?: string, @FormField() filePath?: string - ): Promise { + ): Promise { return updateFile((_filePath ?? filePath)!, file) } @@ -372,6 +398,26 @@ const saveFile = async ( return { status: 'success' } } +const addFolder = async (folderPath: string): Promise => { + const drivePath = getFilesFolder() + + const folderPathFull = path + .join(drivePath, folderPath) + .replace(new RegExp('/', 'g'), path.sep) + + if (!folderPathFull.includes(drivePath)) { + throw new Error('Cannot put folder outside drive.') + } + + if (await folderExists(folderPathFull)) { + throw new Error('Folder already exists.') + } + + await createFolder(folderPathFull) + + return { status: 'success' } +} + const updateFile = async ( filePath: string, multerFile: Express.Multer.File diff --git a/api/src/routes/api/drive.ts b/api/src/routes/api/drive.ts index 1f59271..90fc707 100644 --- a/api/src/routes/api/drive.ts +++ b/api/src/routes/api/drive.ts @@ -11,6 +11,7 @@ import { extractName, fileBodyValidation, fileParamValidation, + folderBodyValidation, folderParamValidation, isZipFile } from '../../utils' @@ -190,6 +191,19 @@ driveRouter.post( } ) +driveRouter.post('/folder', async (req, res) => { + const { error, value: body } = folderBodyValidation(req.body) + + if (error) return res.status(400).send(error.details[0].message) + + try { + const response = await controller.addFolder(body) + res.send(response) + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) + driveRouter.patch( '/file', (...arg) => multerSingle('file', arg), diff --git a/api/src/utils/validation.ts b/api/src/utils/validation.ts index 0789fa5..0dae10c 100644 --- a/api/src/utils/validation.ts +++ b/api/src/utils/validation.ts @@ -143,6 +143,11 @@ export const folderParamValidation = (data: any): Joi.ValidationResult => _folderPath: Joi.string() }).validate(data) +export const folderBodyValidation = (data: any): Joi.ValidationResult => + Joi.object({ + folderPath: Joi.string() + }).validate(data) + export const runCodeValidation = (data: any): Joi.ValidationResult => Joi.object({ code: Joi.string().required(),