From ee57007aa8e40ee5a4e4e8c22485c9523c1f9236 Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Fri, 4 Oct 2019 16:42:01 +0800 Subject: [PATCH] add FileSystemController API endpoint --- .../web/rest/FileSystemController.kt | 67 +++++++++++++++++++ .../web/rest/FileSystemControllerTest.kt | 49 ++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/FileSystemController.kt create mode 100644 komga/src/test/kotlin/org/gotson/komga/interfaces/web/rest/FileSystemControllerTest.kt diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/FileSystemController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/FileSystemController.kt new file mode 100644 index 000000000..7f85afcfd --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/FileSystemController.kt @@ -0,0 +1,67 @@ +package org.gotson.komga.interfaces.web.rest + +import com.fasterxml.jackson.annotation.JsonInclude +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.server.ResponseStatusException +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.Path +import kotlin.streams.toList + +@RestController +@RequestMapping("api/v1/filesystem", produces = [MediaType.APPLICATION_JSON_VALUE]) +class FileSystemController { + + private val fs = FileSystems.getDefault() + + @GetMapping + fun getDirectoryListing( + @RequestParam(name = "path", required = false) path: String? + ): DirectoryListingDto = + if (path.isNullOrEmpty()) { + DirectoryListingDto( + directories = fs.rootDirectories.map { it.toDto() } + ) + } else { + val p = fs.getPath(path) + if (!p.isAbsolute) throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Path must be absolute") + try { + DirectoryListingDto( + parent = (p.parent ?: "").toString(), + directories = Files.list(p).use { dirStream -> + dirStream + .filter { Files.isDirectory(it) } + .sorted() + .map { it.toDto() } + .toList() + } + ) + } catch (e: Exception) { + throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Path does not exist") + } + } +} + +@JsonInclude(JsonInclude.Include.NON_NULL) +data class DirectoryListingDto( + val parent: String? = null, + val directories: List +) + +data class PathDto( + val type: String, + val name: String, + val path: String +) + +fun Path.toDto(): PathDto = + PathDto( + type = if (Files.isDirectory(this)) "directory" else "file", + name = (fileName ?: this).toString(), + path = toString() + ) diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/web/rest/FileSystemControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/web/rest/FileSystemControllerTest.kt new file mode 100644 index 000000000..da02d464a --- /dev/null +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/web/rest/FileSystemControllerTest.kt @@ -0,0 +1,49 @@ +package org.gotson.komga.interfaces.web.rest + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.security.test.context.support.WithAnonymousUser +import org.springframework.security.test.context.support.WithMockUser +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders +import org.springframework.test.web.servlet.result.MockMvcResultMatchers +import java.nio.file.Files + +@ExtendWith(SpringExtension::class) +@SpringBootTest +@AutoConfigureMockMvc(printOnlyOnFailure = false) +class FileSystemControllerTest( + @Autowired private val mockMvc: MockMvc +) { + private val route = "/api/v1/filesystem" + + @Test + @WithAnonymousUser + fun `given anonymous user when getDirectoryListing then return unauthorized`() { + mockMvc.perform(MockMvcRequestBuilders.get(route)) + .andExpect(MockMvcResultMatchers.status().isUnauthorized) + } + + @Test + @WithMockUser(roles = ["USER"]) + fun `given relative path param when getDirectoryListing then return bad request`() { + mockMvc.perform(MockMvcRequestBuilders.get(route) + .param("path", ".")) + .andExpect(MockMvcResultMatchers.status().isBadRequest) + } + + @Test + @WithMockUser(roles = ["USER"]) + fun `given non-existent path param when getDirectoryListing then return bad request`() { + val parent = Files.createTempDirectory(null) + Files.delete(parent) + + mockMvc.perform(MockMvcRequestBuilders.get(route) + .param("path", parent.toString())) + .andExpect(MockMvcResultMatchers.status().isBadRequest) + } +}