Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mbryzek committed Jun 6, 2024
1 parent 3b46487 commit 4077125
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 16 deletions.
6 changes: 5 additions & 1 deletion api/app/db/OrganizationsDao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,12 @@ class OrganizationsDao @Inject() (
name: Option[String] = None,
namespace: Option[String] = None,
isDeleted: Option[Boolean] = Some(false),
deletedAtBefore: Option[DateTime] = None,
limit: Long = 25,
offset: Long = 0
): Seq[Organization] = {
db.withConnection { implicit c =>
authorization.organizationFilter(BaseQuery).
(authorization.organizationFilter(BaseQuery).
equals("organizations.guid", guid).
equals("organizations.key", key).
and(
Expand All @@ -268,6 +269,9 @@ class OrganizationsDao @Inject() (
"organizations.namespace = lower(trim({namespace}))"
}
).bind("namespace", namespace).
and(deletedAtBefore.map { _ =>
"organizations.deleted_at < {deleted_at_before}::timestamptz"
})).bind("deleted_at_before", deletedAtBefore).
and(isDeleted.map(Filters.isDeleted("organizations", _))).
orderBy("lower(organizations.name), organizations.created_at").
limit(limit).
Expand Down
44 changes: 35 additions & 9 deletions api/app/processor/PurgeOldDeletedProcessor.scala
Original file line number Diff line number Diff line change
@@ -1,44 +1,70 @@
package processor

import cats.data.ValidatedNec
import cats.implicits._
import db.UsersDao
import cats.data.ValidatedNec
import db.{Authorization, OrganizationsDao, UsersDao}
import io.apibuilder.task.v0.models.TaskType
import io.flow.postgresql.Query
import org.joda.time.DateTime
import play.api.db.Database

import javax.inject.Inject
import scala.util.{Failure, Success, Try}


class PurgeOldDeletedProcessor @Inject()(
args: TaskProcessorArgs,
db: Database,
usersDao: UsersDao
usersDao: UsersDao,
organizationsDao: OrganizationsDao
) extends TaskProcessor(args, TaskType.PurgeOldDeleted) {
import DeleteMetadata._

override def processRecord(id: String): ValidatedNec[String, Unit] = {
delete("versions", "version_guid", VersionSoft)
delete("applications", "application_guid", ApplicationSoft)
delete("organizations", "organization_guid", OrganizationSoft)
().validNec
purgeOldOrganizations()
}

private[this] def purgeOldOrganizations(): ValidatedNec[String, Unit] = {
organizationsDao.findAll(
Authorization.All,
deletedAtBefore = Some(DateTime.now.minusYears(1)),
isDeleted = Some(true),
limit = 1000,
offset = 0,
).map { org =>
Try {
exec(Query("delete from organizations").equals("guid", org.guid))
} match {
case Success(_) => ().validNec
case Failure(e) => s"Failed to delete org where guid = '${org.guid}': ${e.getMessage}".invalidNec
}
}.sequence.map { _ => () }
}

private[processor] def delete(parentTable: String, column: String, tables: Seq[String]): Unit = {
tables.foreach { table =>
exec(
s"delete from $table where $column in (select guid from $parentTable where deleted_at <= now() - interval '6 months')"
s"""
|delete from $table
| where deleted_at < now() - interval '45 days'
| and $column in (
| select guid from $parentTable where deleted_at <= now() - interval '6 months'
|)
|""".stripMargin
)
}
}

private[this] def exec(q: String): Unit = {
exec(Query(q).bind("deleted_by_guid", usersDao.AdminUser.guid))
}

private[this] def exec(q: Query): Unit = {
db.withConnection { c =>
Query(q)
.bind("deleted_by_guid", usersDao.AdminUser.guid)
.anormSql()
.executeUpdate()(c)
q.anormSql().executeUpdate()(c)
}
()
}
Expand Down
16 changes: 10 additions & 6 deletions api/test/processor/PurgeOldDeletedProcessorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import anorm.SqlParser
import db.Helpers
import io.apibuilder.api.v0.models.{Application, Organization}
import io.flow.postgresql.Query
import org.joda.time.DateTime
import org.scalatestplus.play.PlaySpec
import org.scalatestplus.play.guice.GuiceOneAppPerSuite
import play.api.db.Database
Expand All @@ -12,21 +13,23 @@ import java.util.UUID

class PurgeOldDeletedProcessorSpec extends PlaySpec with GuiceOneAppPerSuite with Helpers {

private[this] def processor: CleanupDeletionsProcessor = app.injector.instanceOf[CleanupDeletionsProcessor]
private[this] def processor: PurgeOldDeletedProcessor = app.injector.instanceOf[PurgeOldDeletedProcessor]
private[this] def database: Database = app.injector.instanceOf[Database]

private[this] def isDeleted(table: String, guid: UUID): Boolean = {
database.withConnection { c =>
Query(s"select count(*) from $table")
.equals("guid", guid)
.as(SqlParser.int(1).*)(c)
}.headOption.exists(_ > 0)
}.head == 0
}

private[this] def softDelete(table: String, guid: UUID): Unit = {
private[this] def softDelete(table: String, guid: UUID, deletedAt: DateTime): Unit = {
database.withConnection { c =>
Query(s"update $table set deleted_at = now() - interval '1 year'")
Query(s"update $table set deleted_at = {deleted_at}::timestamptz, deleted_by_guid = {deleted_by_guid}::uuid")
.equals("guid", guid)
.bind("deleted_at", deletedAt)
.bind("deleted_by_guid", testUser.guid)
.anormSql()
.executeUpdate()(c)
}
Expand All @@ -39,17 +42,18 @@ class PurgeOldDeletedProcessorSpec extends PlaySpec with GuiceOneAppPerSuite wit
def setup(): (Organization, Application) = {
val org = createOrganization()
val app = createApplication(org)
softDelete("applications", app.guid, DateTime.now.minusYears(1))
(org, app)
}

val (_, app) = setup()
val (orgDeleted, appDeleted) = setup()
softDelete("organizations", orgDeleted.guid)
softDelete("organizations", orgDeleted.guid, DateTime.now.minusYears(1))

isAppDeleted(app) mustBe false
isAppDeleted(appDeleted) mustBe false

processor.organizations()
processor.processRecord("periodic")
isAppDeleted(app) mustBe false
isAppDeleted(appDeleted) mustBe true
}
Expand Down

0 comments on commit 4077125

Please sign in to comment.