From 0351e303e62463205c0c3638d0aab1ac93c68737 Mon Sep 17 00:00:00 2001 From: Findeton Date: Wed, 14 Jan 2026 19:51:09 +0100 Subject: [PATCH 1/2] wip --- app/controllers/ElectionsApi.scala | 17 ++++++++++++++++- app/utils/Datastore.scala | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/app/controllers/ElectionsApi.scala b/app/controllers/ElectionsApi.scala index b556200c..9d2ce1f8 100644 --- a/app/controllers/ElectionsApi.scala +++ b/app/controllers/ElectionsApi.scala @@ -1513,6 +1513,10 @@ object ElectionsApi .flatMap { election => val configJson = Json.parse(election.configuration) val config = configJson.validate[ElectionConfig].get + + // If this is a virtual election, also delete datastores for subelections + val virtualSubelections = config.virtualSubelections.getOrElse(Array[Long]()) + val url = eoUrl(config.director, "public_api/delete") WS.url(url).post( Json.obj( @@ -1524,9 +1528,20 @@ object ElectionsApi } else { BadRequest(error(s"EO returned status ${resp.status} with body ${resp.body}", ErrorCodes.EO_ERROR)) } + }.map { _ => + // Delete the parent election from database + DAL.elections.delete(id) + + // Delete datastore for parent election + Datastore.deleteDatastore(id) + + // Delete datastores for all virtual subelections + virtualSubelections.foreach { subElectionId => + Logger.info(s"Deleting datastore for virtual subelection $subElectionId of parent election $id") + Datastore.deleteDatastore(subElectionId) + } } }.map { _ => - DAL.elections.delete(id) Ok(response("ok")) }.recover { case e: NoSuchElementException => diff --git a/app/utils/Datastore.scala b/app/utils/Datastore.scala index ace17262..457bbf2b 100644 --- a/app/utils/Datastore.scala +++ b/app/utils/Datastore.scala @@ -191,6 +191,24 @@ object Datastore { } } + /** deletes all datastore directories for an election (both private and public) */ + def deleteDatastore(electionId: Long) = { + import org.apache.commons.io.FileUtils + + val privatePath = getDirPath(electionId, public = false).toFile + val publicPath = getDirPath(electionId, public = true).toFile + + if (privatePath.exists()) { + Logger.info(s"Deleting private datastore directory for election $electionId: ${privatePath.getAbsolutePath}") + FileUtils.deleteDirectory(privatePath) + } + + if (publicPath.exists()) { + Logger.info(s"Deleting public datastore directory for election $electionId: ${publicPath.getAbsolutePath}") + FileUtils.deleteDirectory(publicPath) + } + } + // UNUSED remove From 97006ca9d8fd9f2a073f6f308c037ca1dfc2aae0 Mon Sep 17 00:00:00 2001 From: Findeton Date: Thu, 15 Jan 2026 18:38:38 +0100 Subject: [PATCH 2/2] wip --- app/utils/Datastore.scala | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/utils/Datastore.scala b/app/utils/Datastore.scala index 457bbf2b..6cbad236 100644 --- a/app/utils/Datastore.scala +++ b/app/utils/Datastore.scala @@ -191,21 +191,36 @@ object Datastore { } } + /** Recursively deletes a directory and all its contents */ + private def deleteDirectoryRecursively(directory: File): Unit = { + if (directory.isDirectory) { + val files = directory.listFiles() + if (files != null) { + files.foreach { file => + if (file.isDirectory) { + deleteDirectoryRecursively(file) + } else { + file.delete() + } + } + } + } + directory.delete() + } + /** deletes all datastore directories for an election (both private and public) */ def deleteDatastore(electionId: Long) = { - import org.apache.commons.io.FileUtils - val privatePath = getDirPath(electionId, public = false).toFile val publicPath = getDirPath(electionId, public = true).toFile if (privatePath.exists()) { Logger.info(s"Deleting private datastore directory for election $electionId: ${privatePath.getAbsolutePath}") - FileUtils.deleteDirectory(privatePath) + deleteDirectoryRecursively(privatePath) } if (publicPath.exists()) { Logger.info(s"Deleting public datastore directory for election $electionId: ${publicPath.getAbsolutePath}") - FileUtils.deleteDirectory(publicPath) + deleteDirectoryRecursively(publicPath) } }