diff --git a/backend/src/main/kotlin/plugins/Databases.kt b/backend/src/main/kotlin/plugins/Databases.kt index df8e040..4befedb 100644 --- a/backend/src/main/kotlin/plugins/Databases.kt +++ b/backend/src/main/kotlin/plugins/Databases.kt @@ -1,5 +1,6 @@ package dev.svitan.plugins +import dev.svitan.routes.ExecutorService import dev.svitan.schemas.ActionService import dev.svitan.schemas.AuthService import io.github.cdimascio.dotenv.Dotenv @@ -20,6 +21,7 @@ fun Application.configureDatabases(dotenv: Dotenv) { password = dbPassword ) - ActionService.init() AuthService.init() + ActionService.init() + ExecutorService.init(dotenv) } diff --git a/backend/src/main/kotlin/routes/Action.kt b/backend/src/main/kotlin/routes/Action.kt index 5ff4e77..11b47c4 100644 --- a/backend/src/main/kotlin/routes/Action.kt +++ b/backend/src/main/kotlin/routes/Action.kt @@ -5,7 +5,6 @@ import dev.svitan.schemas.ActionService import dev.svitan.schemas.AuthService import dev.svitan.schemas.NewActionDTO import dev.svitan.schemas.RunActionDTO -import dev.svitan.schemas.sha256 import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.auth.* @@ -22,7 +21,7 @@ fun Application.routeAction() { call.respond(ActionService.readAll()) } - post("/action") { + put("/action") { val action = call.receive() call.respond(HttpStatusCode.Created, ActionService.create(action)) } @@ -37,11 +36,6 @@ fun Application.routeAction() { post("/action/{id}") { val idRaw = call.parameters["id"] ?: throw BadRequestException("Invalid id") val id = UUID.fromString(idRaw) - } - - put("/action/{id}") { - val idRaw = call.parameters["id"] ?: throw BadRequestException("Invalid id") - val id = UUID.fromString(idRaw) val action = ActionService.read(id) ?: throw NotFoundException() val run = call.receive() @@ -56,6 +50,13 @@ fun Application.routeAction() { call.respond(HttpStatusCode.NoContent) } + patch("/action/{id}") { + val idRaw = call.parameters["id"] ?: throw BadRequestException("Invalid id") + val id = UUID.fromString(idRaw) + val action = call.receive() + call.respond(ActionService.update(id, action)) + } + delete("/action/{id}") { val idRaw = call.parameters["id"] ?: throw BadRequestException("Invalid id") val id = UUID.fromString(idRaw) diff --git a/backend/src/main/kotlin/routes/Auth.kt b/backend/src/main/kotlin/routes/Auth.kt index 77fe346..7546ec1 100644 --- a/backend/src/main/kotlin/routes/Auth.kt +++ b/backend/src/main/kotlin/routes/Auth.kt @@ -19,7 +19,7 @@ fun Application.routeAuth() { call.respond(AuthService.readAll()) } - post("/auth") { + put("/auth") { val auth = call.receive() call.respond(HttpStatusCode.Created, AuthService.create(auth)) } diff --git a/backend/src/main/kotlin/routes/Executor.kt b/backend/src/main/kotlin/routes/Executor.kt new file mode 100644 index 0000000..2b51be5 --- /dev/null +++ b/backend/src/main/kotlin/routes/Executor.kt @@ -0,0 +1,71 @@ +package dev.svitan.routes + +import io.github.cdimascio.dotenv.Dotenv +import java.io.File +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.UUID + +class ExecutorService { + companion object { + var dir: File? = null + + fun init(dotenv: Dotenv) { + val path = dotenv["DATA_DIR"] ?: throw Exception("DATA_DIR not found") + dir = File(path) + + if (!dir!!.exists()) { + val created = dir!!.mkdirs() + if (!created) throw Exception("Unable to create data directory") + } else if (dir!!.isFile) { + throw Exception("DATA_DIR must be a directory") + } else if (!dir!!.canRead() || !dir!!.canWrite() || !dir!!.canExecute()) { + throw Exception("DATA_DIR must be readable, writable, and executable") + } + } + + fun writeFile(uuid: UUID, content: String) { + if (dir == null) throw Exception("FilesystemService not initialized") + + val file = File(dir, "$uuid.sh") + file.writeText(content) + } + + fun deleteFile(uuid: UUID) { + if (dir == null) throw Exception("FilesystemService not initialized") + + val file = File(dir, "$uuid.sh") + if (!file.exists()) throw Exception("File not found") + + val deleted = file.delete() + if (!deleted) throw Exception("Unable to delete file") + } + + fun executeFile(uuid: UUID) { + if (dir == null) throw Exception("FilesystemService not initialized") + + val file = File(dir, "$uuid.sh") + if (!file.exists() || !file.canExecute()) throw Exception("File not found or not executable") + + val now = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + val stdout = File(dir, "$uuid-$now-stdout") + val stderr = File(dir, "$uuid-$now-stderr") + ProcessBuilder("bash", file.absolutePath) + .redirectOutput(ProcessBuilder.Redirect.to(stdout)) + .redirectError(ProcessBuilder.Redirect.to(stderr)) + .start() + } + + fun executeSource(uuid: UUID, source: String) { + if (dir == null) throw Exception("FilesystemService not initialized") + + val now = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + val stdout = File(dir, "$uuid-$now-stdout") + val stderr = File(dir, "$uuid-$now-stderr") + ProcessBuilder("bash", "-c", source) + .redirectOutput(ProcessBuilder.Redirect.to(stdout)) + .redirectError(ProcessBuilder.Redirect.to(stderr)) + .start() + } + } +} diff --git a/backend/src/main/kotlin/schemas/Action.kt b/backend/src/main/kotlin/services/Action.kt similarity index 100% rename from backend/src/main/kotlin/schemas/Action.kt rename to backend/src/main/kotlin/services/Action.kt diff --git a/backend/src/main/kotlin/schemas/Auth.kt b/backend/src/main/kotlin/services/Auth.kt similarity index 100% rename from backend/src/main/kotlin/schemas/Auth.kt rename to backend/src/main/kotlin/services/Auth.kt