Skip to content

Commit

Permalink
Merge pull request #1722 from betagouv/master
Browse files Browse the repository at this point in the history
MEP
  • Loading branch information
ssedoudbgouv authored Sep 2, 2024
2 parents f13d623 + f54c838 commit 19cbedc
Show file tree
Hide file tree
Showing 15 changed files with 271 additions and 23 deletions.
5 changes: 5 additions & 0 deletions app/config/TaskConfiguration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ case class TaskConfiguration(
subscription: SubscriptionTaskConfiguration,
reportClosure: ReportClosureTaskConfiguration,
orphanReportFileDeletion: OrphanReportFileDeletionTaskConfiguration,
oldReportExportDeletion: OldReportExportDeletionTaskConfiguration,
reportReminders: ReportRemindersTaskConfiguration,
inactiveAccounts: InactiveAccountsTaskConfiguration,
companyUpdate: CompanyUpdateTaskConfiguration,
Expand Down Expand Up @@ -39,6 +40,10 @@ case class OrphanReportFileDeletionTaskConfiguration(
startTime: LocalTime
)

case class OldReportExportDeletionTaskConfiguration(
startTime: LocalTime
)

case class ReportRemindersTaskConfiguration(
startTime: LocalTime,
intervalInHours: FiniteDuration,
Expand Down
16 changes: 13 additions & 3 deletions app/loader/SignalConsoApplicationLoader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ import tasks.account.InactiveAccountTask
import tasks.account.InactiveDgccrfAccountReminderTask
import tasks.account.InactiveDgccrfAccountRemoveTask
import tasks.company._
import tasks.report.FileDeletionTask
import tasks.report.OldReportExportDeletionTask
import tasks.report.OrphanReportFileDeletionTask
import tasks.report.ReportClosureTask
import tasks.report.ReportNotificationTask
import tasks.report.ReportRemindersTask
Expand Down Expand Up @@ -500,14 +501,22 @@ class SignalConsoComponents(
messagesApi
)

val fileDeletionTask = new FileDeletionTask(
val orphanReportFileDeletionTask = new OrphanReportFileDeletionTask(
actorSystem,
reportFileRepository,
s3Service,
taskConfiguration,
taskRepository
)

val oldReportExportDeletionTask = new OldReportExportDeletionTask(
actorSystem,
asyncFileRepository,
s3Service,
taskConfiguration,
taskRepository
)

val reportReminderTask = new ReportRemindersTask(
actorSystem,
reportRepository,
Expand Down Expand Up @@ -819,7 +828,8 @@ class SignalConsoComponents(
exportReportsToSFTPTask.schedule()
reportClosureTask.schedule()
reportReminderTask.schedule()
fileDeletionTask.schedule()
orphanReportFileDeletionTask.schedule()
oldReportExportDeletionTask.schedule()
if (applicationConfiguration.task.probe.active) {
probeOrchestrator.scheduleProbeTasks()
}
Expand Down
22 changes: 22 additions & 0 deletions app/repositories/asyncfiles/AsyncFileRepository.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package repositories.asyncfiles

import models._
import org.apache.pekko.NotUsed
import org.apache.pekko.stream.scaladsl.Source
import repositories.CRUDRepository
import repositories.PostgresProfile.api._
import repositories.asyncfiles.AsyncFilesColumnType._
import slick.basic.DatabaseConfig
import slick.jdbc.JdbcProfile
import slick.jdbc.ResultSetConcurrency
import slick.jdbc.ResultSetType

import java.time.OffsetDateTime
import java.util.UUID
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
Expand Down Expand Up @@ -34,11 +39,28 @@ class AsyncFileRepository(override val dbConfig: DatabaseConfig[JdbcProfile])(im
.filterOpt(kind) { case (table, kind) =>
table.kind === kind
}
.filter(_.creationDate >= OffsetDateTime.now().minusDays(1))
.sortBy(_.creationDate.desc)
.to[List]
.result
)

def streamOldReportExports: Source[AsyncFile, NotUsed] = Source
.fromPublisher(
db.stream(
table
.filter(_.creationDate <= OffsetDateTime.now().minusDays(10))
.result
.withStatementParameters(
rsType = ResultSetType.ForwardOnly,
rsConcurrency = ResultSetConcurrency.ReadOnly,
fetchSize = 10000
)
.transactionally
)
)
.log("user")

def deleteByUserId(userId: UUID): Future[Int] = db
.run(
table
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package repositories.asyncfiles
import models.AsyncFile
import models.AsyncFileKind
import models.User
import org.apache.pekko.NotUsed
import org.apache.pekko.stream.scaladsl.Source
import repositories.CRUDRepositoryInterface

import java.util.UUID
Expand All @@ -14,4 +16,6 @@ trait AsyncFileRepositoryInterface extends CRUDRepositoryInterface[AsyncFile] {
def list(user: User, kind: Option[AsyncFileKind] = None): Future[List[AsyncFile]]

def deleteByUserId(userId: UUID): Future[Int]

def streamOldReportExports: Source[AsyncFile, NotUsed]
}
52 changes: 52 additions & 0 deletions app/tasks/report/OldReportExportDeletionTask.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package tasks.report

import cats.implicits.toFunctorOps
import config.TaskConfiguration
import org.apache.pekko.Done
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.stream.Materializer
import org.apache.pekko.stream.scaladsl.Sink
import repositories.asyncfiles.AsyncFileRepositoryInterface
import repositories.tasklock.TaskRepositoryInterface
import services.S3ServiceInterface
import tasks.ScheduledTask
import tasks.model.TaskSettings.DailyTaskSettings
import utils.Logs.RichLogger

import scala.concurrent.ExecutionContext
import scala.concurrent.Future

class OldReportExportDeletionTask(
actorSystem: ActorSystem,
reportFileRepository: AsyncFileRepositoryInterface,
s3Service: S3ServiceInterface,
taskConfiguration: TaskConfiguration,
taskRepository: TaskRepositoryInterface
)(implicit val executionContext: ExecutionContext, mat: Materializer)
extends ScheduledTask(6, "old_report_export_deletion_task", taskRepository, actorSystem, taskConfiguration) {

override val taskSettings = DailyTaskSettings(startTime = taskConfiguration.oldReportExportDeletion.startTime)

override def runTask(): Future[Unit] =
reportFileRepository.streamOldReportExports
.mapAsync(parallelism = 1) { r =>
logger.debug(s"Deleting file ${r.storageFilename}")
for {
_ <- r.storageFilename match {
case Some(storageFilename) =>
s3Service
.delete(storageFilename)
case None => Future.successful(Done)
}
_ = logger.debug(s"File ${r.storageFilename} deleted from s3")
// _ <- reportFileRepository.delete(r.id)
// _ = logger.debug(s"Export File ${r.storageFilename} deleted from signal conso database")
} yield ()
}
.recover { case ex =>
logger.errorWithTitle("old_report_export_deletion_task", "error deleting old report export file", ex)
}
.runWith(Sink.ignore)
.as(())

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ import utils.Logs.RichLogger
import scala.concurrent.ExecutionContext
import scala.concurrent.Future

class FileDeletionTask(
class OrphanReportFileDeletionTask(
actorSystem: ActorSystem,
reportFileRepository: ReportFileRepositoryInterface,
s3Service: S3ServiceInterface,
taskConfiguration: TaskConfiguration,
taskRepository: TaskRepositoryInterface
)(implicit val executionContext: ExecutionContext, mat: Materializer)
extends ScheduledTask(5, "report_file_deletion_task", taskRepository, actorSystem, taskConfiguration) {
extends ScheduledTask(5, "orphan_report_file_deletion_task", taskRepository, actorSystem, taskConfiguration) {

override val taskSettings = DailyTaskSettings(startTime = taskConfiguration.orphanReportFileDeletion.startTime)

Expand Down
9 changes: 5 additions & 4 deletions app/views/mails/consumer/reportAcknowledgment.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,13 @@
<br />
@Messages("ReportAckEmail.email", report.email)
<br/>
<br/>
@if(report.visibleToPro) {
@Messages("ReportAckEmail.contactAgreement", if(report.contactAgreement) {
Messages("ReportAckEmail.yes")
@if(report.contactAgreement) {
<b>@Messages("ReportAckEmail.yes")</b>
} else {
Messages("ReportAckEmail.no")
})
<b>@Messages("ReportAckEmail.no")</b>
}
<br/>
}
</div>
Expand Down
4 changes: 4 additions & 0 deletions conf/common/task.conf
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ task {
start-time = "07:00:00"
start-time = ${?ORPHAN_REPORT_FILE_DELETION_TASK_START_TIME}
}
old-report-export-deletion {
start-time = "07:15:00"
start-time = ${?OLD_REPORT_EXPORT_DELETION_TASK_START_TIME}
}


}
5 changes: 2 additions & 3 deletions conf/messages.en
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,8 @@ ReportAckEmail.consumer=Consumer
ReportAckEmail.lastName=Last Name: {0}
ReportAckEmail.firstName=First Name: {0}
ReportAckEmail.email=Email: {0}
ReportAckEmail.contactAgreement=Consent for contact: {0}
ReportAckEmail.yes=yes
ReportAckEmail.no=no
ReportAckEmail.yes=As requested, your details will be sent to the company
ReportAckEmail.no=As requested, your details will not be sent to the company
ReportAckEmail.understandSignalConso=Understanding SignalConso
ReportAckEmail.yourReport=Your report
ReportAckEmail.influencerName=Influencer: {0}
Expand Down
6 changes: 3 additions & 3 deletions conf/messages.fr
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ ReportAckEmail.consumer=Consommateur
ReportAckEmail.lastName=Nom : {0}
ReportAckEmail.firstName=Prénom : {0}
ReportAckEmail.email=Email : {0}
ReportAckEmail.contactAgreement=Accord pour contact : {0}
ReportAckEmail.yes=oui
ReportAckEmail.no=non

ReportAckEmail.yes=Comme demandé, vos coordonnées seront transmise à l’entreprise
ReportAckEmail.no=Comme demandé, vos coordonnées ne seront pas transmise à l’entreprise
ReportAckEmail.understandSignalConso=Comprendre SignalConso
ReportAckEmail.yourReport=Votre signalement
ReportAckEmail.influencerName=Influenceur: {0}
Expand Down
129 changes: 129 additions & 0 deletions test/tasks/report/OldReportExportDeletionTaskSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package tasks.report

import models.AsyncFileKind.ReportedPhones
import models.AsyncFileKind.ReportedWebsites
import models.AsyncFileKind.Reports
import models.report.Report
import models.AsyncFile
import models.AsyncFileKind
import org.specs2.concurrent.ExecutionEnv
import org.specs2.matcher.FutureMatchers
import org.specs2.mutable.Specification
import play.api.mvc.Results
import play.api.test.WithApplication
import repositories.event.EventFilter
import utils.Constants.ActionEvent.ActionEventValue
import utils.AppSpec
import utils.Fixtures
import utils.TestApp

import java.time.OffsetDateTime
import java.util.UUID
import scala.concurrent.Await
import scala.concurrent.Future
import scala.concurrent.duration.Duration

class OldReportExportDeletionTaskSpec(implicit ee: ExecutionEnv)
extends Specification
with AppSpec
with Results
with FutureMatchers {

val (app, components, queue) = TestApp.buildAppWithS3Queue()

lazy val asyncFileRepository = components.asyncFileRepository
lazy val userRepository = components.userRepository
lazy val fileDeletionTask = components.oldReportExportDeletionTask
lazy val eventRepository = components.eventRepository

val creationDate = OffsetDateTime.parse("2020-01-01T00:00:00Z")
val taskRunDate = OffsetDateTime.parse("2020-06-01T00:00:00Z")
val dateInThePast = taskRunDate.minusDays(5)
val dateInTheFuture = taskRunDate.plusDays(5)

def genUser() =
Fixtures.genUser.sample.get

def genAsyncFile(userId: UUID, creationDate: OffsetDateTime, kind: AsyncFileKind) =
AsyncFile(
UUID.randomUUID(),
userId,
creationDate = creationDate,
Some(UUID.randomUUID().toString),
kind,
Some(UUID.randomUUID().toString)
)

def hasEvent(report: Report, action: ActionEventValue): Future[Boolean] =
eventRepository
.getEvents(report.id, EventFilter(action = Some(action)))
.map(_.nonEmpty)

"OldReportExportDeletionTask should delete old files" >> {

val user = genUser()
val recentReportedWebsitesExport = genAsyncFile(user.id, creationDate = OffsetDateTime.now(), ReportedWebsites)
val oldReportedWebsitesExport =
genAsyncFile(user.id, creationDate = OffsetDateTime.now().minusYears(3), ReportedWebsites)

val recentReportedPhonesExport = genAsyncFile(user.id, creationDate = OffsetDateTime.now(), ReportedPhones)
val oldReportedPhonesExport =
genAsyncFile(user.id, creationDate = OffsetDateTime.now().minusYears(3), ReportedPhones)

val recentReportsExport = genAsyncFile(user.id, creationDate = OffsetDateTime.now(), Reports)
val oldReportsExport = genAsyncFile(user.id, creationDate = OffsetDateTime.now().minusYears(3), Reports)

def setup(): Future[Unit] =
for {
_ <- userRepository.create(user)
_ = queue.add(recentReportedWebsitesExport.storageFilename.get)
_ = queue.add(oldReportedWebsitesExport.storageFilename.get)
_ = queue.add(recentReportedPhonesExport.storageFilename.get)
_ = queue.add(oldReportedPhonesExport.storageFilename.get)
_ = queue.add(recentReportsExport.storageFilename.get)
_ = queue.add(oldReportsExport.storageFilename.get)

_ <- asyncFileRepository.create(recentReportedWebsitesExport)
_ <- asyncFileRepository.create(oldReportedWebsitesExport)
_ <- asyncFileRepository.create(recentReportedPhonesExport)
_ <- asyncFileRepository.create(oldReportedPhonesExport)
_ <- asyncFileRepository.create(recentReportsExport)
_ <- asyncFileRepository.create(oldReportsExport)

_ <- userRepository.get(user.id) map (_.isDefined must beTrue)
_ <- asyncFileRepository.get(recentReportedWebsitesExport.id) map (_.isDefined must beTrue)
_ <- asyncFileRepository.get(oldReportedWebsitesExport.id) map (_.isDefined must beTrue)
} yield ()

def check(): Future[Unit] =
for {
_ <- userRepository.get(user.id) map (_.isDefined must beTrue)

_ <- asyncFileRepository.get(recentReportedWebsitesExport.id) map (_.isDefined must beTrue)
_ <- asyncFileRepository.get(oldReportedWebsitesExport.id) map (_.isDefined must beTrue)
_ <- asyncFileRepository.get(recentReportedPhonesExport.id) map (_.isDefined must beTrue)
_ <- asyncFileRepository.get(oldReportedPhonesExport.id) map (_.isDefined must beTrue)
_ <- asyncFileRepository.get(recentReportsExport.id) map (_.isDefined must beTrue)
_ <- asyncFileRepository.get(oldReportsExport.id) map (_.isDefined must beTrue)

_ = queue.contains(recentReportedWebsitesExport.storageFilename.get) must beTrue
_ = queue.contains(oldReportedWebsitesExport.storageFilename.get) must beFalse
_ = queue.contains(recentReportedPhonesExport.storageFilename.get) must beTrue
_ = queue.contains(oldReportedPhonesExport.storageFilename.get) must beFalse
_ = queue.contains(recentReportsExport.storageFilename.get) must beTrue
_ = queue.contains(oldReportsExport.storageFilename.get) must beFalse

} yield ()

new WithApplication(app) {
Await.result(
for {
_ <- setup()
_ <- fileDeletionTask.runTask()
_ <- check()
} yield (),
Duration.Inf
)
}
}
}
Loading

0 comments on commit 19cbedc

Please sign in to comment.