diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/folder/FolderHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/folder/FolderHelper.java index 522cc374d16d..634ecb99abff 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/folder/FolderHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/folder/FolderHelper.java @@ -8,6 +8,7 @@ import com.dotmarketing.portlets.contentlet.business.HostAPI; import com.dotmarketing.portlets.folders.business.FolderAPI; import com.dotmarketing.portlets.folders.model.Folder; +import com.dotmarketing.util.InodeUtils; import com.dotmarketing.util.Logger; import com.dotmarketing.util.UtilMethods; import com.liferay.portal.model.User; @@ -102,7 +103,7 @@ private Map folderToMap (final Folder folder) { */ public Folder loadFolderByURI(String siteName, User user, String uri) throws DotDataException, DotSecurityException { Host host = hostAPI.findByName(siteName,user,true); - Folder ret = null; + Folder ret; if(uri.equals("/")){ ret = folderAPI.findSystemFolder(); }else{ @@ -208,7 +209,7 @@ public List findSubFoldersPathByParentPath(final String /** * Will find the subfolders living directly under the host passed. - * If pathToSearch is sent is gonna filter the path of the subfolder by it. + * If pathToSearch is sent is going to filter the path of the subfolder by it. */ private List findSubfoldersUnderHost(final String siteId, final String pathToSearch, final User user) @@ -237,7 +238,7 @@ private List findSubfoldersUnderHost(final String siteId } /** - * Will find the subfolders living directly under the host passed and the last valid folder (spliting the pathToSearch by the last '/'). + * Will find the subfolders living directly under the host passed and the last valid folder (splitting the pathToSearch by the last '/'). * And filter the results by what is left after the last '/'. */ private List findSubfoldersUnderFolder(final String siteId, @@ -272,4 +273,12 @@ private List findSubfoldersUnderFolder(final String site return subFolders; } + /** + * Check if the folder is valid and has an inode. + * @param folder folder to check + * @return true if the folder exists in the database or if the folder is not null + */ + public boolean isValidFolder(final Folder folder) { + return UtilMethods.isSet(folder) && InodeUtils.isSet(folder.getInode()); + } } diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/folder/FolderResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/folder/FolderResource.java index 87b26afbd6cb..dec1fa4bc5e9 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/folder/FolderResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/folder/FolderResource.java @@ -21,8 +21,12 @@ import com.dotmarketing.util.UtilMethods; import com.liferay.portal.model.User; import com.liferay.util.StringPool; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import javax.servlet.http.HttpSession; import javax.ws.rs.DELETE; import javax.ws.rs.PUT; import org.glassfish.jersey.server.JSONP; @@ -35,7 +39,6 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -69,13 +72,15 @@ public FolderResource(final WebResource webResource, /** * Delete one or more path for a site - * @param httpServletRequest - * @param httpServletResponse - * @param paths - * @param siteName + * @param httpServletRequest The current instance of the {@link HttpServletRequest}. + * @param httpServletResponse The current instance of the {@link HttpServletResponse}. + * @param paths paths to delete + * @param siteName site name * @return List of folders deleted - * @throws DotSecurityException - * @throws DotDataException + * @throws IllegalArgumentException if the site name is not found + * @throws DoesNotExistException if the folder does not exist + * @throws DotSecurityException if the user does not have permission to delete the folder + * @throws DotDataException if there is an error deleting the folder */ @DELETE @Path("/{siteName}") @@ -83,6 +88,30 @@ public FolderResource(final WebResource webResource, @NoCache @Consumes(MediaType.APPLICATION_JSON) @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) + @Operation( + summary = "Delete one or more path for a site", + description = "Delete one or more path for a site if they exist" + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Folders deleted successfully", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{\n" + + " \"entity\": [\n" + + " \"/folder-1/folder-2/target-folder\"\n" + + " ],\n" + + " \"errors\": [],\n" + + " \"i18nMessagesMap\": {},\n" + + " \"messages\": [],\n" + + " \"pagination\": null,\n" + + " \"permissions\": []\n" + + "}" + ) + ) + ), + @ApiResponse(responseCode = "401", description = "Unauthorized access"), + @ApiResponse(responseCode = "404", description = "Folders not found"), + @ApiResponse(responseCode = "403", description = "Insufficient permissions to delete folders") + }) public final Response deleteFolders(@Context final HttpServletRequest httpServletRequest, @Context final HttpServletResponse httpServletResponse, final List paths, @@ -100,23 +129,21 @@ public final Response deleteFolders(@Context final HttpServletRequest httpServle final List deletedFolders = new ArrayList<>(); final Host host = APILocator.getHostAPI().findByName(siteName, user, true); - if(!UtilMethods.isSet(host)) { - - throw new IllegalArgumentException(String.format(" Couldn't find any host with name `%s` ",siteName)); + if (!UtilMethods.isSet(host)) { + throw new IllegalArgumentException( + String.format(" Couldn't find any host with name `%s` ", siteName)); } - Logger.debug(this, ()-> "Deleting the folders: " + paths); + Logger.debug(this, () -> "Deleting the folders: " + paths); for (final String path : paths) { - final Folder folder = folderHelper.loadFolderByURI(host.getIdentifier(), user, path); - if (null != folder) { - Logger.debug(this, ()-> "Deleting the folder: " + path); - folderHelper.deleteFolder (folder, user); + if (folderHelper.isValidFolder(folder)) { + Logger.debug(this, () -> "Deleting the folder: " + path); + folderHelper.deleteFolder(folder, user); deletedFolders.add(path); } else { - Logger.error(this, "The folder does not exists: " + path); throw new DoesNotExistException("The folder does not exists: " + path); } diff --git a/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml b/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml index ff8b91ca47ce..8bc47b99e46e 100644 --- a/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml +++ b/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml @@ -8617,6 +8617,7 @@ paths: - Folders /v1/folder/{siteName}: delete: + description: Delete one or more path for a site if they exist operationId: deleteFolders parameters: - in: path @@ -8632,11 +8633,25 @@ paths: items: type: string responses: - default: + "200": content: - application/javascript: {} - application/json: {} - description: default response + application/json: + example: + entity: + - /folder-1/folder-2/target-folder + errors: [] + i18nMessagesMap: {} + messages: [] + pagination: null + permissions: [] + description: Folders deleted successfully + "401": + description: Unauthorized access + "403": + description: Insufficient permissions to delete folders + "404": + description: Folders not found + summary: Delete one or more path for a site tags: - Folders /v1/forgotpassword: diff --git a/dotcms-postman/src/main/resources/postman/FolderResource.postman_collection.json b/dotcms-postman/src/main/resources/postman/FolderResource.postman_collection.json index 883097da78b5..aae2d17e0c01 100644 --- a/dotcms-postman/src/main/resources/postman/FolderResource.postman_collection.json +++ b/dotcms-postman/src/main/resources/postman/FolderResource.postman_collection.json @@ -858,6 +858,63 @@ } ] }, + { + "name": "deleteFolder", + "item": [ + { + "name": "deleteNonExistedFolder", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Response status code is 404\", function () {", + " pm.expect(pm.response.code).to.equal(404);", + "});", + "", + "pm.test(\"Response should contain a message field\", function () {", + " const responseData = pm.response.json();", + " ", + " pm.expect(responseData).to.be.an('object');", + " pm.expect(responseData).to.have.property('message');", + "});", + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "[\"/folder-1/folder-2/target-folder\"]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/folder/default", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "folder", + "default" + ] + } + }, + "response": [] + } + ] + }, { "name": "Get Folder Success", "event": [ diff --git a/test-karate/src/test/java/graphql/ftm/deleteFolder.feature b/test-karate/src/test/java/graphql/ftm/deleteFolder.feature index 9554cce5e923..05362725d05f 100644 --- a/test-karate/src/test/java/graphql/ftm/deleteFolder.feature +++ b/test-karate/src/test/java/graphql/ftm/deleteFolder.feature @@ -1,5 +1,29 @@ Feature: Delete a Folder - Scenario: Delete a folder on the given path if exists + Scenario: Delete an existing folder + # Create a folder + * def assetPath = '//default' + path + '/' + * def createRequest = + """ + { + "assetPath": "#(assetPath)", + "data": { + "title": "application/containers/banner", + "showOnMenu": false, + "sortOrder": 1, + "defaultAssetType": "FileAsset" + } + } + """ + + Given url baseUrl + '/api/v1/assets/folders' + And headers commonHeaders + And request createRequest + When method POST + Then status 200 + * def errors = call extractErrors response + * match errors == [] + + # Delete an existing folder (previously created) Given url baseUrl + '/api/v1/folder/default' And headers commonHeaders And request @@ -9,4 +33,14 @@ Feature: Delete a Folder When method DELETE Then status 200 * def errors = call extractErrors response - * match errors == [] \ No newline at end of file + * match errors == [] + + Scenario: Try to delete a non-existing folder + * def nonExistingFolder = '/does-not-exist-folder' + + Given url baseUrl + '/api/v1/folder/default' + And headers commonHeaders + And request ["#(nonExistingFolder)"] + When method DELETE + Then status 404 + * match response.message == 'The folder does not exists: ' + nonExistingFolder \ No newline at end of file