From a12fcb8346edf8db652d28ed416167b6bdd79e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Del=C3=A8gue?= Date: Wed, 9 Oct 2024 14:55:21 +0200 Subject: [PATCH] Expirable consents --- build.sbt | 13 +- docker-compose.dev.yml | 4 +- docker-compose.yml | 4 +- manual/build.sbt | 2 +- nio-provider/app/actor/EventActor.scala | 5 +- .../app/configuration/Configuration.scala | 2 +- .../app/controllers/UserDataController.scala | 10 +- nio-provider/app/utils/Results.scala | 18 +- nio-provider/build.sbt | 24 +-- .../app/configuration/Configuration.scala | 3 +- .../app/controllers/AccountController.scala | 26 ++- .../app/controllers/ConsentController.scala | 46 +++--- .../app/controllers/ControllerUtils.scala | 5 +- .../app/controllers/DeletionController.scala | 3 +- nio-server/app/controllers/ErrorManager.scala | 2 +- .../app/controllers/EventController.scala | 13 +- .../controllers/ExtractionController.scala | 4 +- .../app/controllers/HomeController.scala | 4 +- .../app/controllers/MetricsController.scala | 15 +- .../controllers/NioAccountController.scala | 3 +- .../controllers/OrganisationController.scala | 25 +-- .../OrganisationOfferController.scala | 1 - .../app/controllers/TenantController.scala | 5 +- .../app/controllers/UserController.scala | 10 +- .../controllers/UserExtractController.scala | 5 +- .../app/db/CatchupLockMongoDatastore.scala | 15 +- .../app/db/ExtractionTaskMongoDataStore.scala | 1 - .../db/LastConsentFactMongoDataStore.scala | 7 +- nio-server/app/db/MongoDataStore.scala | 1 - nio-server/app/db/MongoOpsDataStore.scala | 7 +- .../app/db/OrganisationMongoDataStore.scala | 12 +- nio-server/app/db/TenantMongoDataStore.scala | 1 - nio-server/app/db/UserMongoDataStore.scala | 27 +-- nio-server/app/filters/Auth0Filter.scala | 7 +- nio-server/app/filters/OtoroshiFilter.scala | 2 +- nio-server/app/libs/io.scala | 2 - nio-server/app/libs/xml/XmlParser.scala | 2 +- nio-server/app/loader/NioLoader.scala | 4 +- nio-server/app/loader/Starter.scala | 2 +- .../app/messaging/KafkaMessageBroker.scala | 7 +- nio-server/app/models/Account.scala | 2 +- nio-server/app/models/ApiKey.scala | 4 +- nio-server/app/models/Auth.scala | 1 - nio-server/app/models/ConsentFact.scala | 154 +++++++++++------- nio-server/app/models/DeletionTask.scala | 36 ++-- nio-server/app/models/ExtractionTask.scala | 55 ++++--- nio-server/app/models/NioAccount.scala | 4 +- nio-server/app/models/Organisation.scala | 25 ++- nio-server/app/models/PagedConsentFacts.scala | 8 +- nio-server/app/models/PagedUsers.scala | 8 +- nio-server/app/models/Permission.scala | 53 +++++- nio-server/app/models/PermissionGroup.scala | 10 +- nio-server/app/models/Tenant.scala | 8 +- nio-server/app/models/User.scala | 6 +- nio-server/app/models/UserExtract.scala | 3 +- nio-server/app/models/UserExtractTask.scala | 9 +- nio-server/app/models/common.scala | 16 +- nio-server/app/models/events.scala | 121 +++++++------- .../AccessibleOfferManagerService.scala | 1 - .../app/service/CatchupNioEventService.scala | 23 +-- .../app/service/ConsentManagerService.scala | 17 +- nio-server/app/service/MailService.scala | 2 - .../app/service/OfferManagerService.scala | 70 ++++---- nio-server/app/utils/BSONUtils.scala | 20 +-- nio-server/app/utils/DateUtils.scala | 2 +- nio-server/app/utils/DefaultLoader.scala | 5 +- nio-server/app/utils/IdGenerator.scala | 4 +- nio-server/app/utils/Results.scala | 32 ++-- nio-server/app/utils/S3Manager.scala | 19 +-- nio-server/app/utils/SecureEvent.scala | 17 +- nio-server/build.sbt | 19 ++- nio-server/conf/application.conf | 2 - .../conf/{default.conf => dataset.conf} | 3 + nio-server/conf/dev.conf | 4 + nio-server/conf/routes | 4 +- nio-server/javascript/.nvmrc | 1 + .../javascript/src/nio/pages/Consents.js | 5 +- .../src/nio/pages/GroupPermissionPage.js | 7 + .../src/nio/services/ConsentService.js | 2 +- .../controllers/ConsentControllerSpec.scala | 46 +++++- .../OrganisationControllerSpec.scala | 16 +- .../test/models/ModelValidationSpec.scala | 4 +- nio-server/test/models/OrganisationSpec.scala | 68 ++++++++ nio-server/test/utils/TestUtils.scala | 2 +- project/Dependencies.scala | 3 + project/plugins.sbt | 4 +- version.sbt | 2 +- 87 files changed, 703 insertions(+), 573 deletions(-) rename nio-server/conf/{default.conf => dataset.conf} (96%) create mode 100644 nio-server/conf/dev.conf create mode 100644 nio-server/javascript/.nvmrc diff --git a/build.sbt b/build.sbt index f96ea9d0..37d2591a 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,9 @@ -import ReleaseTransformations._ +import Dependencies._scalaVersion +import ReleaseTransformations.* name := """nio""" organization := "fr.maif" -scalaVersion := "2.13.11" +scalaVersion := _scalaVersion lazy val root = (project in file(".")) .aggregate( @@ -13,6 +14,14 @@ lazy val root = (project in file(".")) lazy val `nio-server` = project lazy val `nio-provider` = project +inThisBuild( + List( + scalaVersion := _scalaVersion, + semanticdbEnabled := true, + semanticdbVersion := scalafixSemanticdb.revision + ) +) + releaseProcess := Seq[ReleaseStep]( checkSnapshotDependencies, // : ReleaseStep inquireVersions, // : ReleaseStep diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index e4611cf2..1a5c53ba 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -6,7 +6,7 @@ services: - 27017:27017 zookeeper: - image: confluentinc/cp-zookeeper:3.2.1 + image: confluentinc/cp-zookeeper:5.2.3 ports: - 32182:32181 expose: @@ -16,7 +16,7 @@ services: - ZOOKEEPER_TICK_TIME=2000 kafka: - image: confluentinc/cp-kafka:3.2.1 + image: confluentinc/cp-kafka:5.2.3 ports: - 29092:29092 expose: diff --git a/docker-compose.yml b/docker-compose.yml index 2e14a8a7..bba3ae6c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: ports: - 27017:27017 zookeeper: - image: confluentinc/cp-zookeeper:3.2.1 + image: confluentinc/cp-zookeeper:5.2.3 ports: - 32182:32181 environment: @@ -15,7 +15,7 @@ services: - "moby:127.0.0.1" - "localhost: 127.0.0.1" kafka: - image: confluentinc/cp-kafka:3.2.1 + image: confluentinc/cp-kafka:5.2.3 ports: - 29092:29092 depends_on: diff --git a/manual/build.sbt b/manual/build.sbt index 1e029617..b7e0de51 100644 --- a/manual/build.sbt +++ b/manual/build.sbt @@ -1,7 +1,7 @@ name := """nio-manual""" organization := "fr.maif" version := "2.0.4" -scalaVersion := "2.12.4" +scalaVersion := _scalaVersion lazy val docs = (project in file(".")) .enablePlugins(ParadoxPlugin) diff --git a/nio-provider/app/actor/EventActor.scala b/nio-provider/app/actor/EventActor.scala index c26cc4b2..225b918f 100644 --- a/nio-provider/app/actor/EventActor.scala +++ b/nio-provider/app/actor/EventActor.scala @@ -6,13 +6,12 @@ import play.api.libs.json.Json import utils.NioLogger object EventActor { - def props(out: ActorRef) = Props(new EventActor(out)) + def props(out: ActorRef): Props = Props(new EventActor(out)) } class EventActor(out: ActorRef) extends Actor { - override def preStart = - context.system.eventStream.subscribe(self, classOf[NioEvent]) + override def preStart(): Unit = context.system.eventStream.subscribe(self, classOf[NioEvent]) override def receive: Receive = { case e: NioEvent => NioLogger.info(s"Event actor received a message : ${e.tYpe}") diff --git a/nio-provider/app/configuration/Configuration.scala b/nio-provider/app/configuration/Configuration.scala index e8dbbc56..d9afa7ec 100644 --- a/nio-provider/app/configuration/Configuration.scala +++ b/nio-provider/app/configuration/Configuration.scala @@ -8,7 +8,7 @@ import scala.concurrent.duration.FiniteDuration object NioConfiguration { import pureconfig.generic.auto._ - implicit def hint[T] = + implicit def hint[T]: ProductHint[T] = ProductHint[T](ConfigFieldMapping(CamelCase, CamelCase)) def apply(config: Configuration): NioConfiguration = diff --git a/nio-provider/app/controllers/UserDataController.scala b/nio-provider/app/controllers/UserDataController.scala index f8c416ff..6283757b 100644 --- a/nio-provider/app/controllers/UserDataController.scala +++ b/nio-provider/app/controllers/UserDataController.scala @@ -1,7 +1,6 @@ package controllers import java.io.FileInputStream - import actor.EventActor import org.apache.pekko.actor.ActorSystem import org.apache.pekko.stream.Materializer @@ -9,8 +8,9 @@ import org.apache.pekko.stream.scaladsl.{Source, StreamConverters} import org.apache.pekko.util.ByteString import auth.AuthActionWithEmail import configuration.Env +import play.api.libs.Files import play.api.libs.streams.ActorFlow -import play.api.mvc.{AbstractController, ControllerComponents, WebSocket} +import play.api.mvc.{AbstractController, Action, ControllerComponents, MultipartFormData, WebSocket} import service.NioService import utils.NioLogger @@ -24,15 +24,15 @@ class UserDataController( implicit val actorSystem: ActorSystem, implicit val ec: ExecutionContext ) extends AbstractController(cc) { - implicit val mat = Materializer(actorSystem) + implicit val mat: Materializer = Materializer(actorSystem) - def listen() = WebSocket.accept[String, String] { request => + def listen(): WebSocket = WebSocket.accept[String, String] { request => ActorFlow.actorRef { out => EventActor.props(out) } } - def uploadFile(tenant: String, orgKey: String, userId: String, name: String) = + def uploadFile(tenant: String, orgKey: String, userId: String, name: String): Action[MultipartFormData[Files.TemporaryFile]] = AuthAction.async(parse.multipartFormData) { implicit req => NioLogger.info(s"upload file $name") val src: Source[ByteString, _] = diff --git a/nio-provider/app/utils/Results.scala b/nio-provider/app/utils/Results.scala index 6aa8fb40..191deb6d 100644 --- a/nio-provider/app/utils/Results.scala +++ b/nio-provider/app/utils/Results.scala @@ -39,7 +39,7 @@ object Result { import cats.instances.all._ import cats.syntax.semigroup._ - implicit val format = Json.format[AppErrors] + implicit val format: OFormat[AppErrors] = Json.format[AppErrors] def fromJsError(jsError: Seq[(JsPath, Seq[JsonValidationError])]): AppErrors = { val fieldErrors = jsError.map { case (k, v) => @@ -63,9 +63,9 @@ object Result { } implicit val monoid: Monoid[AppErrors] = new Monoid[AppErrors] { - override def empty = AppErrors() + override def empty: AppErrors = AppErrors() - override def combine(x: AppErrors, y: AppErrors) = { + override def combine(x: AppErrors, y: AppErrors): AppErrors = { val errors = x.errors ++ y.errors val fieldErrors = mergeMap(x.fieldErrors, y.fieldErrors) AppErrors(errors, fieldErrors) @@ -96,7 +96,7 @@ object Result { } case class ImportResult(success: Int = 0, errors: AppErrors = AppErrors()) { - def isError = !errors.isEmpty + def isError: Boolean = !errors.isEmpty def toJson: JsValue = ImportResult.format.writes(this) } @@ -105,18 +105,18 @@ object ImportResult { import cats.syntax.semigroup._ - implicit val format = Json.format[ImportResult] + implicit val format: OFormat[ImportResult] = Json.format[ImportResult] - implicit val monoid = new Monoid[ImportResult] { - override def empty = ImportResult() + implicit val monoid: Monoid[ImportResult] = new Monoid[ImportResult] { + override def empty: ImportResult = ImportResult() - override def combine(x: ImportResult, y: ImportResult) = (x, y) match { + override def combine(x: ImportResult, y: ImportResult): ImportResult = (x, y) match { case (ImportResult(s1, e1), ImportResult(s2, e2)) => ImportResult(s1 + s2, e1 |+| e2) } } - def error(e: ErrorMessage) = ImportResult(errors = AppErrors(errors = Seq(e))) + def error(e: ErrorMessage): ImportResult = ImportResult(errors = AppErrors(errors = Seq(e))) def fromResult[T](r: Result[T]): ImportResult = r match { case Right(_) => ImportResult(success = 1) diff --git a/nio-provider/build.sbt b/nio-provider/build.sbt index 41dd6cd3..9201df0d 100644 --- a/nio-provider/build.sbt +++ b/nio-provider/build.sbt @@ -9,7 +9,7 @@ lazy val `nio-provider` = (project in file(".")) .enablePlugins(NoPublish) .disablePlugins(BintrayPlugin) -scalaVersion := "2.13.14" +scalaVersion := _scalaVersion resolvers ++= Seq( Resolver.jcenterRepo, @@ -42,11 +42,11 @@ scalacOptions ++= Seq( /// ASSEMBLY CONFIG -mainClass in assembly := Some("play.core.server.ProdServerStart") -test in assembly := {} -assemblyJarName in assembly := "nio-provider.jar" -fullClasspath in assembly += Attributed.blank(PlayKeys.playPackageAssets.value) -assemblyMergeStrategy in assembly := { +assembly / mainClass := Some("play.core.server.ProdServerStart") +assembly / test := {} +assembly / assemblyJarName := "nio-provider.jar" +assembly / fullClasspath += Attributed.blank(PlayKeys.playPackageAssets.value) +assembly / assemblyMergeStrategy := { case PathList("javax", xs @ _*) => MergeStrategy.first case PathList("META-INF", "native", xs @ _*) => MergeStrategy.first case PathList("org", "apache", "commons", "logging", xs @ _*) => MergeStrategy.discard @@ -59,14 +59,14 @@ assemblyMergeStrategy in assembly := { case PathList(ps @ _*) if ps.contains("buildinfo") => MergeStrategy.discard case PathList(ps @ _*) if ps.last endsWith "reflection-config.json" => MergeStrategy.first case o => - val oldStrategy = (assemblyMergeStrategy in assembly).value + val oldStrategy = (assembly / assemblyMergeStrategy).value oldStrategy(o) } lazy val packageAll = taskKey[Unit]("PackageAll") packageAll := { - (dist in Compile).value - (assembly in Compile).value + (Compile / dist).value + (Compile / assembly).value } /// DOCKER CONFIG @@ -74,9 +74,9 @@ packageAll := { dockerExposedPorts := Seq( 9000 ) -packageName in Docker := "nio-provider" +Docker / packageName := "nio-provider" -maintainer in Docker := "MAIF Team " +Docker / maintainer := "MAIF Team " dockerBaseImage := "openjdk:8" @@ -109,4 +109,4 @@ dockerEntrypoint ++= Seq( dockerUpdateLatest := true -packageName in Universal := s"nio-provider" +Universal / packageName := s"nio-provider" diff --git a/nio-server/app/configuration/Configuration.scala b/nio-server/app/configuration/Configuration.scala index 58c91e20..acc5e3eb 100644 --- a/nio-server/app/configuration/Configuration.scala +++ b/nio-server/app/configuration/Configuration.scala @@ -9,8 +9,7 @@ import scala.concurrent.duration.FiniteDuration object NioConfiguration { - implicit def hint[T] = - ProductHint[T](ConfigFieldMapping(CamelCase, CamelCase)) + implicit def hint[T]: ProductHint[T] = ProductHint[T](ConfigFieldMapping(CamelCase, CamelCase)) def apply(config: Configuration): NioConfiguration = ConfigSource.fromConfig(config.underlying).at("nio").loadOrThrow[NioConfiguration] diff --git a/nio-server/app/controllers/AccountController.scala b/nio-server/app/controllers/AccountController.scala index 683c7501..7810d0ea 100644 --- a/nio-server/app/controllers/AccountController.scala +++ b/nio-server/app/controllers/AccountController.scala @@ -1,15 +1,15 @@ package controllers -import auth.{AuthAction, SecuredAction, SecuredAuthContext} +import auth.SecuredAuthContext import controllers.ErrorManager.ErrorManagerResult import db.AccountMongoDataStore +import libs.xmlorjson.XmlOrJson import messaging.KafkaMessageBroker import models._ import utils.NioLogger -import play.api.mvc.{ActionBuilder, AnyContent, ControllerComponents} +import play.api.mvc.{Action, ActionBuilder, AnyContent, ControllerComponents} import scala.concurrent.{ExecutionContext, Future} -import scala.collection.Seq class AccountController( val AuthAction: ActionBuilder[SecuredAuthContext, AnyContent], @@ -21,24 +21,22 @@ class AccountController( implicit val readable: ReadableEntity[Account] = Account - def find(tenant: String, accountId: String) = AuthAction.async { implicit req => + def find(tenant: String, accountId: String): Action[AnyContent] = AuthAction.async { implicit req => accountStore .findByAccountId(tenant, accountId) .map { - case Some(account) => - renderMethod(account) - case None => - "error.unknown.account".notFound() + case Some(account) => renderMethod(account) + case None => "error.unknown.account".notFound() } } - def findAll(tenant: String, page: Int, pageSize: Int) = AuthAction.async { implicit req => + def findAll(tenant: String, page: Int, pageSize: Int): Action[AnyContent] = AuthAction.async { implicit req => accountStore .findAll(tenant, page, pageSize) .map(accounts => renderMethod(Accounts(accounts))) } - def create(tenant: String) = AuthAction(bodyParser).async { implicit req => + def create(tenant: String): Action[XmlOrJson] = AuthAction(bodyParser).async { implicit req => req.body.read[Account] match { case Left(error) => NioLogger.error(s"Invalid account format $error") @@ -60,7 +58,7 @@ class AccountController( } } - def update(tenant: String, accountId: String) = + def update(tenant: String, accountId: String): Action[XmlOrJson] = AuthAction(bodyParser).async { implicit req => req.body.read[Account] match { case Left(error) => @@ -82,16 +80,16 @@ class AccountController( ) renderMethod(account) } - case None => + case None => Future.successful("error.account.not.found".notFound()) } - case Right(account) if accountId != account.accountId => + case Right(_) => Future.successful("error.accountId.is.immutable".badRequest()) } } - def delete(tenant: String, accountId: String) = AuthAction.async { implicit req => + def delete(tenant: String, accountId: String): Action[AnyContent] = AuthAction.async { implicit req => accountStore.findByAccountId(tenant, accountId).flatMap { case Some(account) => accountStore diff --git a/nio-server/app/controllers/ConsentController.scala b/nio-server/app/controllers/ConsentController.scala index 660a59a8..b3b59b80 100644 --- a/nio-server/app/controllers/ConsentController.scala +++ b/nio-server/app/controllers/ConsentController.scala @@ -6,9 +6,9 @@ import org.apache.pekko.http.scaladsl.util.FastFuture import org.apache.pekko.stream.{FlowShape, Materializer} import org.apache.pekko.stream.scaladsl.{Flow, Framing, GraphDSL, Merge, Partition, Sink, Source} import org.apache.pekko.util.ByteString -import auth.{AuthAction, SecuredAuthContext} +import auth.SecuredAuthContext import controllers.ErrorManager.{AppErrorManagerResult, ErrorManagerResult, ErrorWithStatusManagerResult} -import db.{ConsentFactMongoDataStore, LastConsentFactMongoDataStore, OrganisationMongoDataStore, UserMongoDataStore} +import db.{ConsentFactMongoDataStore, LastConsentFactMongoDataStore, OrganisationMongoDataStore} import libs.io.IO import libs.io._ import libs.xmlorjson.XmlOrJson @@ -33,7 +33,6 @@ import scala.util.hashing.MurmurHash3 class ConsentController( val AuthAction: ActionBuilder[SecuredAuthContext, AnyContent], val cc: ControllerComponents, - val userStore: UserMongoDataStore, val consentFactStore: ConsentFactMongoDataStore, val lastConsentFactMongoDataStore: LastConsentFactMongoDataStore, val organisationStore: OrganisationMongoDataStore, @@ -46,9 +45,9 @@ class ConsentController( implicit val readable: ReadableEntity[ConsentFact] = ConsentFact implicit val readablePartial: ReadableEntity[PartialConsentFact] = PartialConsentFact - implicit val materializer = Materializer(system) + implicit val materializer: Materializer = Materializer(system) - def getTemplate(tenant: String, orgKey: String, maybeUserId: Option[String], maybeOfferKeys: Option[Seq[String]]) = + def getTemplate(tenant: String, orgKey: String, maybeUserId: Option[String], maybeOfferKeys: Option[Seq[String]]): Action[AnyContent] = AuthAction.async { implicit req => import cats.data._ import cats.implicits._ @@ -87,7 +86,7 @@ class ConsentController( key = pg.key, label = pg.label, consents = pg.permissions.map { p => - Consent(key = p.key, label = p.label, checked = p.checkDefault()) + Consent(key = p.key, label = p.label, checked = p.checkDefault(), expiredAt = p.getValidityPeriod) } ) @@ -131,15 +130,15 @@ class ConsentController( } - def find(tenant: String, orgKey: String, userId: String) = AuthAction.async { implicit req => + def find(tenant: String, orgKey: String, userId: String, showExpiredConsents: Boolean): Action[AnyContent] = AuthAction.async { implicit req => import cats.data._ import cats.implicits._ EitherT(findConsentFacts(tenant, orgKey, userId, req.authInfo.offerRestrictionPatterns)) - .fold(error => error.renderError(), consentFact => renderMethod(consentFact)) + .fold(error => error.renderError(), consentFact => renderMethod(consentFact.filterExpiredConsent(showExpiredConsents))) } - def findConsentFacts( + private def findConsentFacts( tenant: String, orgKey: String, userId: String, @@ -160,7 +159,7 @@ class ConsentController( Right(consentManagerService.consentFactWithAccessibleOffers(consentFact, offerRestrictionPatterns)) } - def getConsentFactHistory(tenant: String, orgKey: String, userId: String, page: Int = 0, pageSize: Int = 10) = + def getConsentFactHistory(tenant: String, orgKey: String, userId: String, page: Int = 0, pageSize: Int = 10): Action[AnyContent] = AuthAction.async { implicit req => consentFactStore .findAllByUserId(tenant, userId, page, pageSize) @@ -188,7 +187,7 @@ class ConsentController( case Right(o) if o.userId != userId => NioLogger.error(s"error.userId.is.immutable : userId in path $userId // userId on body ${o.userId}") Future.successful("error.userId.is.immutable".badRequest()) - case Right(consentFact) if consentFact.userId == userId => + case Right(consentFact) => val cf: ConsentFact = ConsentFact.addOrgKey(consentFact, orgKey) (cf.offers, req.authInfo.offerRestrictionPatterns) match { @@ -272,11 +271,11 @@ class ConsentController( } yield result).merge } - val newLineSplit = Framing.delimiter(ByteString("\n"), 10000, allowTruncation = true) - val toJson = Flow[ByteString] via newLineSplit map (_.utf8String) filterNot (_.isEmpty) map (l => Json.parse(l)) - def ndJson(implicit ec: ExecutionContext): BodyParser[Source[JsValue, _]] = BodyParser(_ => Accumulator.source[ByteString].map(s => Right(s.via(toJson)))(ec)) + val newLineSplit: Flow[ByteString, ByteString, NotUsed] = Framing.delimiter(ByteString("\n"), 10000, allowTruncation = true) + val toJson: Flow[ByteString, JsValue, NotUsed] = Flow[ByteString] via newLineSplit map (_.utf8String) filterNot (_.isEmpty) map (l => Json.parse(l)) + private def ndJson(implicit ec: ExecutionContext): BodyParser[Source[JsValue, _]] = BodyParser(_ => Accumulator.source[ByteString].map(s => Right(s.via(toJson)))(ec)) - object ImportError { + private object ImportError { implicit val format: OFormat[ImportError] = Json.format[ImportError] } case class ImportError(message: String, detailedError: JsValue = JsNull, command: JsValue = JsNull) @@ -285,7 +284,7 @@ class ConsentController( ImportResult(errorsCount = 1, errors = List(ImportError(message, command = command))) } - implicit val format = Json.format[ImportResult] + implicit val format: OFormat[ImportResult] = Json.format[ImportResult] } case class ImportResult(successCount: Int = 0, errorsCount: Int = 0, errors: List[ImportError] = List.empty) { def combine (other: ImportResult) : ImportResult = @@ -296,7 +295,7 @@ class ConsentController( ) } - def sharding[In, Out](parallelism: Int, aFlow: Flow[(String, In), Out, NotUsed]) = + private def sharding[In, Out](parallelism: Int, aFlow: Flow[(String, In), Out, NotUsed]): Flow[(String, In), Out, NotUsed] = Flow.fromGraph { GraphDSL.create() { implicit b => import GraphDSL.Implicits._ @@ -315,7 +314,7 @@ class ConsentController( } - def batchImport(tenant: String, orgKey: String) = AuthAction.async(ndJson) { implicit req => + def batchImport(tenant: String, orgKey: String): Action[Source[JsValue, _]] = AuthAction.async(ndJson) { implicit req => val result: Future[JsValue] = req.body .map(json => ((json \ "userId").validate[String].getOrElse(""), json)) .via(sharding(10, Flow[(String, JsValue)].mapAsync(1) { case (_, json) => @@ -396,7 +395,7 @@ class ConsentController( NioLogger.error(s"error during consent fact saving $error") ImportResult(errorsCount = 1, errors = List(ImportError(message = "Error during update", detailedError = error.appErrors.asJson(), command = json))) }, - consentFactSaved => ImportResult(successCount = 1) + _ => ImportResult(successCount = 1) ) // case create or update offers and some patterns are specified case (Some(offers), Some(_)) => @@ -432,7 +431,7 @@ class ConsentController( lazy val defaultParSize: Int = sys.env.get("DEFAULT_PAR_SIZE").map(_.toInt).getOrElse(6) - def download(tenant: String) = AuthAction { implicit req => + def download(tenant: String): Action[AnyContent] = AuthAction { implicit req => val src = lastConsentFactMongoDataStore .streamAllBSON( tenant, @@ -447,7 +446,7 @@ class ConsentController( ) } - def downloadBulked(tenant: String) = AuthAction { implicit req => + def downloadBulked(tenant: String): Action[AnyContent] = AuthAction { _ => NioLogger.info(s"Downloading consents (using bulked reads) from tenant $tenant") import reactivemongo.pekkostream.cursorProducer @@ -478,7 +477,7 @@ class ConsentController( ) } - def deleteOffer(tenant: String, orgKey: String, userId: String, offerKey: String) = AuthAction.async { implicit req => + def deleteOffer(tenant: String, orgKey: String, userId: String, offerKey: String): Action[AnyContent] = AuthAction.async { implicit req => req.authInfo.offerRestrictionPatterns match { case Some(_) if !accessibleOfferService.accessibleOfferKey(offerKey, req.authInfo.offerRestrictionPatterns) => NioLogger.error(s"offer $offerKey unauthorized") @@ -507,6 +506,9 @@ class ConsentController( ) } } + case None => + NioLogger.error(s"offer $offerKey unauthorized") + Future.successful(s"error.offer.$offerKey.unauthorized".unauthorized()) } } } diff --git a/nio-server/app/controllers/ControllerUtils.scala b/nio-server/app/controllers/ControllerUtils.scala index 87275ffa..95fd4a5f 100644 --- a/nio-server/app/controllers/ControllerUtils.scala +++ b/nio-server/app/controllers/ControllerUtils.scala @@ -3,7 +3,7 @@ package controllers import auth.AuthContext import libs.xmlorjson.XmlOrJson import models.ModelTransformAs -import play.api.{mvc, Logger} +import play.api.mvc import play.api.http.{HeaderNames, MimeTypes} import play.api.libs.json.JsValue import play.api.mvc.Results.Status @@ -13,7 +13,6 @@ import utils.Result.AppErrors import scala.concurrent.ExecutionContext import scala.xml.Elem -import scala.collection.Seq trait ReadableEntity[T] { @@ -84,7 +83,7 @@ abstract class ControllerUtils(val controller: ControllerComponents)(implicit va Left(AppErrors.error("error.missing.content.type")) } - implicit def renderError = new ErrorManagerSuite { + implicit def renderError: ErrorManagerSuite = new ErrorManagerSuite { override def convert(appErrors: AppErrors, status: mvc.Results.Status)(implicit req: Request[Any]): Result = { val missingAcceptedTypes: Boolean = !req.headers .get(HeaderNames.ACCEPT) diff --git a/nio-server/app/controllers/DeletionController.scala b/nio-server/app/controllers/DeletionController.scala index 4c6bc632..13d8117a 100644 --- a/nio-server/app/controllers/DeletionController.scala +++ b/nio-server/app/controllers/DeletionController.scala @@ -1,7 +1,7 @@ package controllers import org.apache.pekko.actor.ActorSystem -import auth.{AuthAction, SecuredAction, SecuredAuthContext} +import auth.SecuredAuthContext import db.{ConsentFactMongoDataStore, DeletionTaskMongoDataStore, OrganisationMongoDataStore, UserMongoDataStore} import messaging.KafkaMessageBroker import models._ @@ -9,7 +9,6 @@ import utils.NioLogger import play.api.mvc.{ActionBuilder, AnyContent, ControllerComponents} import ErrorManager.ErrorManagerResult import ErrorManager.AppErrorManagerResult -import scala.collection.Seq import scala.concurrent.{ExecutionContext, Future} diff --git a/nio-server/app/controllers/ErrorManager.scala b/nio-server/app/controllers/ErrorManager.scala index 38a9c297..944195d5 100644 --- a/nio-server/app/controllers/ErrorManager.scala +++ b/nio-server/app/controllers/ErrorManager.scala @@ -84,7 +84,7 @@ case class AppErrorWithStatus(appErrors: AppErrors = AppErrors(), status: Status object AppErrorWithStatus { - implicit val monoidInstance = Monoid.instance[AppErrorWithStatus](AppErrorWithStatus(), (err1, err2) => + implicit val monoidInstance: Monoid[AppErrorWithStatus] = Monoid.instance[AppErrorWithStatus](AppErrorWithStatus(), (err1, err2) => AppErrorWithStatus(err1.appErrors ++ err2.appErrors, err1.status) ) def apply(message: String): AppErrorWithStatus = AppErrorWithStatus( diff --git a/nio-server/app/controllers/EventController.scala b/nio-server/app/controllers/EventController.scala index b65036d7..610a3e85 100644 --- a/nio-server/app/controllers/EventController.scala +++ b/nio-server/app/controllers/EventController.scala @@ -1,13 +1,13 @@ package controllers -import auth.{AuthAction, SecuredAction, SecuredAuthContext} -import javax.inject.{Inject, Singleton} +import auth.SecuredAuthContext + import messaging.KafkaMessageBroker import models.NioEvent import play.api.libs.EventSource import play.api.libs.EventSource.{EventDataExtractor, EventNameExtractor} import play.api.libs.json.Json -import play.api.mvc.{ActionBuilder, AnyContent, ControllerComponents} +import play.api.mvc.{Action, ActionBuilder, AnyContent, ControllerComponents} import scala.concurrent.ExecutionContext @@ -18,11 +18,10 @@ class EventController( )(implicit val ec: ExecutionContext) extends ControllerUtils(cc) { - private implicit val nameExtractor = EventNameExtractor[NioEvent](_ => None) - private implicit val dataExtractor = - EventDataExtractor[NioEvent](event => Json.stringify(event.asJson())) + private implicit val nameExtractor: EventNameExtractor[NioEvent] = EventNameExtractor[NioEvent](_ => None) + private implicit val dataExtractor: EventDataExtractor[NioEvent] = EventDataExtractor[NioEvent](event => Json.stringify(event.asJson())) - def events(tenant: String) = AuthAction { implicit req => + def events(tenant: String): Action[AnyContent] = AuthAction { _ => Ok.chunked(broker.events(tenant) via EventSource.flow) .as("text/event-stream") } diff --git a/nio-server/app/controllers/ExtractionController.scala b/nio-server/app/controllers/ExtractionController.scala index 0d8e6b99..1c246a04 100644 --- a/nio-server/app/controllers/ExtractionController.scala +++ b/nio-server/app/controllers/ExtractionController.scala @@ -43,7 +43,7 @@ class ExtractionController( val broker: KafkaMessageBroker ) extends ControllerUtils(cc) { - implicit val mat = Materializer(system) + implicit val mat: Materializer = Materializer(system) val fileBodyParser: BodyParser[Source[ByteString, _]] = BodyParser { _ => Accumulator.source[ByteString].map(s => Right(s)) @@ -142,7 +142,7 @@ class ExtractionController( appState.files.find(_.name == name) match { case None => Future.successful("error.unknown.appId".notFound()) - case Some(fileMetadata) => + case Some(_) => (if (s3Conf.v4auth) { upload(tenant, req.task, appId, name) } else { diff --git a/nio-server/app/controllers/HomeController.scala b/nio-server/app/controllers/HomeController.scala index c3cd01f3..27d45142 100644 --- a/nio-server/app/controllers/HomeController.scala +++ b/nio-server/app/controllers/HomeController.scala @@ -86,7 +86,7 @@ class HomeController( } } - def login = AuthAction { ctx => + def login = AuthAction { _ => Ok(views.html.indexLogin(env)) } @@ -110,7 +110,7 @@ class HomeController( } } - def swagger() = AuthAction { req => + def swagger() = AuthAction { _ => Ok(swaggerContent).as("application/json") } } diff --git a/nio-server/app/controllers/MetricsController.scala b/nio-server/app/controllers/MetricsController.scala index 7b3fa30f..018a33c4 100644 --- a/nio-server/app/controllers/MetricsController.scala +++ b/nio-server/app/controllers/MetricsController.scala @@ -1,14 +1,13 @@ package controllers import org.apache.pekko.actor.ActorSystem -import auth.{AuthAction, SecuredAction, SecuredAuthContext} -import com.fasterxml.jackson.databind.ObjectMapper +import auth.SecuredAuthContext import configuration.Env import controllers.ErrorManager.ErrorManagerResult import db.TenantMongoDataStore import messaging.KafkaSettings import org.apache.kafka.clients.consumer.Consumer -import play.api.mvc.{ActionBuilder, AnyContent, ControllerComponents} +import play.api.mvc.{Action, ActionBuilder, AnyContent, ControllerComponents} import scala.jdk.CollectionConverters._ import scala.concurrent.{ExecutionContext, Future} @@ -22,12 +21,9 @@ class MetricsController( )(implicit ec: ExecutionContext) extends ControllerUtils(cc) { - val mapper = new ObjectMapper() + private val blockingExecutionContext = actorSystem.dispatchers.lookup("blocking-dispatcher") - val blockingExecutionContext = - actorSystem.dispatchers.lookup("blocking-dispatcher") - - def healthCheck() = AuthAction.async { implicit req => + def healthCheck(): Action[AnyContent] = AuthAction.async { implicit req => req.headers.get(env.healthCheckConfig.header) match { case Some(secret) if secret == env.healthCheckConfig.secret => tenantStore @@ -49,8 +45,7 @@ class MetricsController( Ok } - case None => - Future.successful("error.missing.secret".unauthorized()) + case _ => Future.successful("error.missing.secret".unauthorized()) } } diff --git a/nio-server/app/controllers/NioAccountController.scala b/nio-server/app/controllers/NioAccountController.scala index a70c313a..c676a3de 100644 --- a/nio-server/app/controllers/NioAccountController.scala +++ b/nio-server/app/controllers/NioAccountController.scala @@ -11,7 +11,6 @@ import play.api.mvc.{Action, ActionBuilder, AnyContent, ControllerComponents} import utils.Sha import scala.concurrent.{ExecutionContext, Future} -import scala.collection.Seq class NioAccountController( val AuthAction: ActionBuilder[SecuredAuthContext, AnyContent], @@ -33,7 +32,7 @@ class NioAccountController( nioAccountMongoDataStore .findByEmail(nioAccount.email) .flatMap { - case Some(nioAccountStored) => + case Some(_) => Future.successful( s"error.account.email.already.used" .conflict() diff --git a/nio-server/app/controllers/OrganisationController.scala b/nio-server/app/controllers/OrganisationController.scala index 40f12d31..6549a8ce 100644 --- a/nio-server/app/controllers/OrganisationController.scala +++ b/nio-server/app/controllers/OrganisationController.scala @@ -3,7 +3,7 @@ package controllers import org.apache.pekko.actor.ActorSystem import org.apache.pekko.stream.Materializer import org.apache.pekko.util.ByteString -import auth.{AuthAction, SecuredAction, SecuredAuthContext} +import auth.SecuredAuthContext import db._ import models._ import messaging.KafkaMessageBroker @@ -16,6 +16,7 @@ import reactivemongo.api.bson.BSONObjectID import scala.concurrent.{ExecutionContext, Future} import ErrorManager.ErrorManagerResult import ErrorManager.AppErrorManagerResult +import libs.xmlorjson.XmlOrJson class OrganisationController( val AuthAction: ActionBuilder[SecuredAuthContext, AnyContent], @@ -32,7 +33,7 @@ class OrganisationController( implicit val readable: ReadableEntity[Organisation] = OrganisationDraft implicit val materializer: Materializer = Materializer(system) - def create(tenant: String) = AuthAction.async(bodyParser) { implicit req => + def create(tenant: String): Action[XmlOrJson] = AuthAction.async(bodyParser) { implicit req => tenantDataStore.findByKey(tenant).flatMap { case Some(_) => req.body.read[Organisation] match { @@ -73,7 +74,7 @@ class OrganisationController( } // update if exists - def replaceDraftIfExists(tenant: String, orgKey: String) = + def replaceDraftIfExists(tenant: String, orgKey: String): Action[XmlOrJson] = AuthAction.async(bodyParser) { implicit req => req.body.read[Organisation] match { case Left(error) => @@ -81,7 +82,7 @@ class OrganisationController( Future.successful(error.badRequest()) case Right(o) if o.key != orgKey => Future.successful("error.invalid.organisation.key".badRequest()) - case Right(o) if o.key == orgKey => + case Right(o) => OrganisationValidator.validateOrganisation(o) match { case Left(error) => NioLogger.error("Organisation is not valid " + error) @@ -112,7 +113,7 @@ class OrganisationController( } } - def findAllReleasedByKey(tenant: String, orgKey: String) = AuthAction.async { implicit req => + def findAllReleasedByKey(tenant: String, orgKey: String): Action[AnyContent] = AuthAction.async { implicit req => ds.findDraftByKey(tenant, orgKey).flatMap { case None => Future.successful("error.organisation.not.found".notFound()) @@ -123,7 +124,7 @@ class OrganisationController( } } - def findLastReleasedByKey(tenant: String, orgKey: String) = AuthAction.async { implicit req => + def findLastReleasedByKey(tenant: String, orgKey: String): Action[AnyContent] = AuthAction.async { implicit req => ds.findLastReleasedByKey(tenant, orgKey).map { case None => "error.organisation.not.found".notFound() case Some(o) => @@ -131,7 +132,7 @@ class OrganisationController( } } - def findDraftByKey(tenant: String, orgKey: String) = AuthAction.async { implicit req => + def findDraftByKey(tenant: String, orgKey: String): Action[AnyContent] = AuthAction.async { implicit req => ds.findDraftByKey(tenant, orgKey).map { case None => "error.organisation.not.found".notFound() case Some(org) => @@ -139,7 +140,7 @@ class OrganisationController( } } - def findReleasedByKeyAndVersionNum(tenant: String, orgKey: String, version: Int) = AuthAction.async { implicit req => + def findReleasedByKeyAndVersionNum(tenant: String, orgKey: String, version: Int): Action[AnyContent] = AuthAction.async { implicit req => ds.findReleasedByKeyAndVersionNum(tenant, orgKey, version).map { case None => "error.organisation.not.found".notFound() case Some(org) => @@ -147,7 +148,7 @@ class OrganisationController( } } - def releaseDraft(tenant: String, orgKey: String) = + def releaseDraft(tenant: String, orgKey: String): Action[AnyContent] = AuthAction.async { implicit req => ds.findDraftByKey(tenant, orgKey).flatMap { case Some(previousOrganisationDraft) => @@ -228,7 +229,7 @@ class OrganisationController( } } - def list(tenant: String) = AuthAction.async { implicit req => + def list(tenant: String): Action[AnyContent] = AuthAction.async { implicit req => ds.findAllLatestReleasesOrDrafts(tenant).map { orgas => val lightOrgas = orgas.map(OrganisationLight.from) @@ -236,7 +237,7 @@ class OrganisationController( } } - def delete(tenant: String, orgKey: String) = AuthAction.async { implicit req => + def delete(tenant: String, orgKey: String): Action[AnyContent] = AuthAction.async { implicit req => for { maybeLastRelease <- ds.findLastReleasedByKey(tenant, orgKey) maybeDraft <- ds.findDraftByKey(tenant, orgKey) @@ -265,7 +266,7 @@ class OrganisationController( } yield res } - def download(tenant: String, from: String, to: String) = AuthAction.async { implicit req => + def download(tenant: String, from: String, to: String): Action[AnyContent] = AuthAction.async { _ => ds.streamAllLatestReleasesOrDraftsByDate(tenant, from, to).map { source => val src = source .map(Json.stringify) diff --git a/nio-server/app/controllers/OrganisationOfferController.scala b/nio-server/app/controllers/OrganisationOfferController.scala index 71c71724..5957c817 100644 --- a/nio-server/app/controllers/OrganisationOfferController.scala +++ b/nio-server/app/controllers/OrganisationOfferController.scala @@ -24,7 +24,6 @@ import play.api.mvc.{ActionBuilder, AnyContent, ControllerComponents} import reactivemongo.api.bson.BSONObjectID import service.{ConsentManagerService, OfferManagerService} -import java.time.format.DateTimeFormatter import scala.collection.Seq import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success} diff --git a/nio-server/app/controllers/TenantController.scala b/nio-server/app/controllers/TenantController.scala index 76ab2868..ed2d08c5 100644 --- a/nio-server/app/controllers/TenantController.scala +++ b/nio-server/app/controllers/TenantController.scala @@ -1,15 +1,14 @@ package controllers -import auth.{AuthAction, SecuredAction, SecuredAuthContext} +import auth.SecuredAuthContext import configuration.Env import controllers.ErrorManager._ import db._ import messaging.KafkaMessageBroker import models.{Tenant, TenantCreated, TenantDeleted, Tenants} import play.api.mvc.{ActionBuilder, AnyContent, ControllerComponents} -import play.api.{Configuration, Logger} +import play.api.Configuration import utils.NioLogger -import utils.Result.AppErrors import scala.concurrent.{ExecutionContext, Future} class TenantController( diff --git a/nio-server/app/controllers/UserController.scala b/nio-server/app/controllers/UserController.scala index eb208033..71bf0ed7 100644 --- a/nio-server/app/controllers/UserController.scala +++ b/nio-server/app/controllers/UserController.scala @@ -3,7 +3,7 @@ package controllers import org.apache.pekko.actor.ActorSystem import org.apache.pekko.stream.Materializer import org.apache.pekko.util.ByteString -import auth.{AuthAction, SecuredAction, SecuredAuthContext} +import auth.SecuredAuthContext import db.UserMongoDataStore import models.PagedUsers import play.api.http.HttpEntity @@ -19,7 +19,7 @@ class UserController( )(implicit val ec: ExecutionContext, system: ActorSystem) extends ControllerUtils(cc) { - implicit val materializer = Materializer(system) + implicit val materializer: Materializer = Materializer(system) def listByOrganisation( tenant: String, @@ -27,7 +27,7 @@ class UserController( page: Int = 0, pageSize: Int = 10, maybeUserId: Option[String] - ) = AuthAction.async { implicit req => + ): Action[AnyContent] = AuthAction.async { implicit req => ds.findAllByOrgKey(tenant, orgKey, page, pageSize, maybeUserId).map { case (users, count) => val pagedUsers = PagedUsers(page, pageSize, count, users) @@ -35,7 +35,7 @@ class UserController( } } - def listAll(tenant: String, page: Int = 0, pageSize: Int = 10, maybeUserId: Option[String]) = + def listAll(tenant: String, page: Int = 0, pageSize: Int = 10, maybeUserId: Option[String]): Action[AnyContent] = AuthAction.async { implicit req => ds.findAll(tenant, page, pageSize, maybeUserId).map { case (users, count) => val pagedUsers = PagedUsers(page, pageSize, count, users) @@ -44,7 +44,7 @@ class UserController( } } - def download(tenant: String) = AuthAction.async { implicit req => + def download(tenant: String): Action[AnyContent] = AuthAction.async { _ => ds.streamAll(tenant).map { source => val src = source .map(Json.stringify) diff --git a/nio-server/app/controllers/UserExtractController.scala b/nio-server/app/controllers/UserExtractController.scala index 64dcf1da..8149cb3a 100644 --- a/nio-server/app/controllers/UserExtractController.scala +++ b/nio-server/app/controllers/UserExtractController.scala @@ -6,7 +6,7 @@ import org.apache.pekko.actor.ActorSystem import org.apache.pekko.stream.IOResult import org.apache.pekko.stream.scaladsl.{Source, StreamConverters} import org.apache.pekko.util.ByteString -import auth.{AuthAction, SecuredAction, SecuredAuthContext} +import auth.SecuredAuthContext import com.auth0.jwt.JWT import com.auth0.jwt.algorithms.Algorithm import configuration.Env @@ -18,7 +18,6 @@ import models._ import java.time.{Clock, LocalDateTime} import utils.NioLogger import play.api.http.HttpEntity -import play.api.libs.Files import play.api.libs.json.Json import play.api.libs.streams.Accumulator import play.api.mvc._ @@ -284,7 +283,7 @@ class UserExtractController( } def streamFile: BodyParser[Source[ByteString, _]] = - BodyParser { req => + BodyParser { _ => Accumulator.source[ByteString].map(s => Right(s)) } diff --git a/nio-server/app/db/CatchupLockMongoDatastore.scala b/nio-server/app/db/CatchupLockMongoDatastore.scala index d60a35a6..647617a7 100644 --- a/nio-server/app/db/CatchupLockMongoDatastore.scala +++ b/nio-server/app/db/CatchupLockMongoDatastore.scala @@ -2,12 +2,11 @@ package db import org.apache.pekko.http.scaladsl.util.FastFuture -import java.time.{Clock, Instant, LocalDateTime, ZoneId} +import java.time.{Instant, LocalDateTime, ZoneId} import utils.NioLogger import play.api.libs.json._ import play.modules.reactivemongo.ReactiveMongoApi import reactivemongo.api.indexes.{Index, IndexType} -import reactivemongo.api.bson.BSONDocument import reactivemongo.api.bson.collection.BSONCollection import scala.concurrent.{ExecutionContext, Future} @@ -19,8 +18,6 @@ class CatchupLockMongoDatastore(val reactiveMongoApi: ReactiveMongoApi)(implicit import reactivemongo.api.bson._ import reactivemongo.play.json.compat._ - import models.ExtractionTaskStatus._ - import lax._ import bson2json._ import json2bson._ @@ -36,7 +33,7 @@ class CatchupLockMongoDatastore(val reactiveMongoApi: ReactiveMongoApi)(implicit implicit def format: Format[CatchupLock] = CatchupLock.catchupLockFormats - def init() = { + def init(): Future[Unit] = { NioLogger.debug("### init lock datastore ###") storedCollection.flatMap { col => @@ -51,7 +48,7 @@ class CatchupLockMongoDatastore(val reactiveMongoApi: ReactiveMongoApi)(implicit def storedCollection: Future[BSONCollection] = reactiveMongoApi.database.map(_.collection("catchupLock")) - def createLock(tenant: String) = + def createLock(tenant: String): Future[Boolean] = storedCollection .flatMap( _.find(Json.obj("tenant" -> tenant)) @@ -62,12 +59,12 @@ class CatchupLockMongoDatastore(val reactiveMongoApi: ReactiveMongoApi)(implicit storedCollection.flatMap { coll => coll.insert.one(format.writes(CatchupLock(tenant)).as[JsObject]).map(_.writeErrors.isEmpty) } - case Some(catchupItem) => + case Some(_) => NioLogger.debug(s"Stored collection already locked for tenant $tenant") FastFuture.successful(false) } - def findLock(tenant: String) = + def findLock(tenant: String): Future[Option[CatchupLock]] = storedCollection .flatMap( _.find(Json.obj("tenant" -> tenant)) @@ -98,7 +95,7 @@ object CatchupLock { ) } - implicit val jodaDateFormat = new Format[LocalDateTime] { + implicit val jodaDateFormat: Format[LocalDateTime] = new Format[LocalDateTime] { override def reads(d: JsValue): JsResult[LocalDateTime] = JsSuccess(Instant .ofEpochMilli(d.as[JsObject].\("$date").as[JsNumber].value.toLong) diff --git a/nio-server/app/db/ExtractionTaskMongoDataStore.scala b/nio-server/app/db/ExtractionTaskMongoDataStore.scala index ae47ca0d..57502750 100644 --- a/nio-server/app/db/ExtractionTaskMongoDataStore.scala +++ b/nio-server/app/db/ExtractionTaskMongoDataStore.scala @@ -18,7 +18,6 @@ class ExtractionTaskMongoDataStore(val mongoApi: ReactiveMongoApi)(implicit val import reactivemongo.api.bson._ import reactivemongo.play.json.compat._ import models.ExtractionTaskStatus._ - import lax._ import bson2json._ import json2bson._ diff --git a/nio-server/app/db/LastConsentFactMongoDataStore.scala b/nio-server/app/db/LastConsentFactMongoDataStore.scala index 1cd68cf6..d8e78f9f 100644 --- a/nio-server/app/db/LastConsentFactMongoDataStore.scala +++ b/nio-server/app/db/LastConsentFactMongoDataStore.scala @@ -8,26 +8,25 @@ import controllers.AppErrorWithStatus import models._ import utils.NioLogger import play.api.libs.json.{JsValue, Json, OFormat} -import play.api.mvc.Results.NotFound import play.modules.reactivemongo.ReactiveMongoApi -import reactivemongo.pekkostream.cursorProducer import reactivemongo.api.{Cursor, ReadPreference} import reactivemongo.api.bson.collection.BSONCollection import reactivemongo.api.indexes.{Index, IndexType} import utils.BSONUtils import utils.Result.{AppErrors, ErrorMessage} +import scala.annotation.nowarn import scala.concurrent.{ExecutionContext, Future} import scala.util.Failure -import scala.collection.{immutable, Seq} +import scala.collection.{Seq, immutable} +@nowarn("msg=Will be removed when provided by Play-JSON itself") class LastConsentFactMongoDataStore(val mongoApi: ReactiveMongoApi)(implicit val executionContext: ExecutionContext) extends MongoDataStore[ConsentFact] { import reactivemongo.api.bson._ import reactivemongo.play.json.compat._ import reactivemongo.pekkostream._ - import lax._ import bson2json._ import json2bson._ val format: OFormat[ConsentFact] = models.ConsentFact.consentFactOFormats diff --git a/nio-server/app/db/MongoDataStore.scala b/nio-server/app/db/MongoDataStore.scala index fa88f168..76840353 100644 --- a/nio-server/app/db/MongoDataStore.scala +++ b/nio-server/app/db/MongoDataStore.scala @@ -28,7 +28,6 @@ trait MongoDataStore[T] { import reactivemongo.play.json.compat._ import reactivemongo.pekkostream._ import bson2json._ - import json2bson._ def mongoApi: ReactiveMongoApi implicit def format: OFormat[T] diff --git a/nio-server/app/db/MongoOpsDataStore.scala b/nio-server/app/db/MongoOpsDataStore.scala index 1615f3d5..b3b1aeee 100644 --- a/nio-server/app/db/MongoOpsDataStore.scala +++ b/nio-server/app/db/MongoOpsDataStore.scala @@ -5,9 +5,11 @@ import org.apache.pekko.stream.scaladsl.Source import reactivemongo.api.{Cursor, ReadPreference} import reactivemongo.api.bson.collection.BSONCollection +import scala.annotation.nowarn import scala.concurrent.{ExecutionContext, Future} import scala.collection.Seq +@nowarn("msg=Will be removed when provided by Play-JSON itself") object MongoOpsDataStore { implicit class MongoDataStore(coll: BSONCollection)(implicit executionContext: ExecutionContext) { @@ -31,7 +33,6 @@ object MongoOpsDataStore { def updateByQuery[T](query: JsObject, update: JsObject)(implicit OFormat: OFormat[T]): Future[Boolean] = { import json2bson._ - import bson2json._ // implicit val writer = implicitly[BSONDocumentWriter[T]] val builder = coll.update(ordered = false) val updates = builder.element(q = query, u = update, upsert = true, multi = false) @@ -45,14 +46,11 @@ object MongoOpsDataStore { } def findOneById[T](id: String)(implicit oformat: OFormat[T]): Future[Option[T]] = { - import json2bson._ import bson2json._ findOne(Json.obj("_id" -> id)) } def findOneByQuery[T](query: JsObject)(implicit oformat: OFormat[T]): Future[Option[T]] = { - import json2bson._ - import bson2json._ findOne(query) } @@ -137,7 +135,6 @@ object MongoOpsDataStore { private def delete[T](query: JsObject)(implicit oformat: OFormat[T]): Future[Boolean] = { import json2bson._ - import bson2json._ val builder = coll.delete(ordered = false) builder .element(q = query, limit = None, collation = None) diff --git a/nio-server/app/db/OrganisationMongoDataStore.scala b/nio-server/app/db/OrganisationMongoDataStore.scala index 6dda2527..acbe3bcc 100644 --- a/nio-server/app/db/OrganisationMongoDataStore.scala +++ b/nio-server/app/db/OrganisationMongoDataStore.scala @@ -4,23 +4,25 @@ import org.apache.pekko.http.scaladsl.util.FastFuture import org.apache.pekko.stream.Materializer import controllers.AppErrorWithStatus import models._ +import org.apache.pekko.stream.scaladsl.Source import utils.NioLogger import play.api.libs.json._ import play.modules.reactivemongo.ReactiveMongoApi -import reactivemongo.pekkostream.cursorProducer +import reactivemongo.api.indexes.Index.Default import reactivemongo.api.indexes.{Index, IndexType} import utils.Result.{AppErrors, ErrorMessage} +import scala.annotation.nowarn import scala.concurrent.{ExecutionContext, Future} -import scala.collection.{immutable, Seq} +import scala.collection.{Seq, immutable} +@nowarn("msg=Will be removed when provided by Play-JSON itself") class OrganisationMongoDataStore(val mongoApi: ReactiveMongoApi)(implicit val executionContext: ExecutionContext) extends MongoDataStore[Organisation] { import reactivemongo.api.bson._ import reactivemongo.play.json.compat._ import reactivemongo.pekkostream._ - import lax._ import bson2json._ import json2bson._ @@ -28,7 +30,7 @@ class OrganisationMongoDataStore(val mongoApi: ReactiveMongoApi)(implicit val ex override def collectionName(tenant: String) = s"$tenant-organisations" - override def indices = Seq( + override def indices: Seq[Default] = Seq( Index(immutable.Seq("orgKey" -> IndexType.Ascending), name = Some("orgKey"), unique = false, sparse = true), Index( immutable.Seq("orgKey" -> IndexType.Ascending, "version.num" -> IndexType.Ascending), @@ -133,7 +135,7 @@ class OrganisationMongoDataStore(val mongoApi: ReactiveMongoApi)(implicit val ex findManyByQuery(tenant, query) } - def streamAllLatestReleasesOrDraftsByDate(tenant: String, from: String, to: String)(implicit m: Materializer) = { + def streamAllLatestReleasesOrDraftsByDate(tenant: String, from: String, to: String)(implicit m: Materializer): Future[Source[JsValue, Future[State]]] = { val query = Json.obj( "$or" -> Json.arr( Json.obj( diff --git a/nio-server/app/db/TenantMongoDataStore.scala b/nio-server/app/db/TenantMongoDataStore.scala index 6d7fb6aa..a11e778b 100644 --- a/nio-server/app/db/TenantMongoDataStore.scala +++ b/nio-server/app/db/TenantMongoDataStore.scala @@ -16,7 +16,6 @@ class TenantMongoDataStore(val reactiveMongoApi: ReactiveMongoApi)(implicit val import reactivemongo.api.bson._ import reactivemongo.play.json.compat._ import reactivemongo.pekkostream._ - import lax._ import bson2json._ import json2bson._ diff --git a/nio-server/app/db/UserMongoDataStore.scala b/nio-server/app/db/UserMongoDataStore.scala index bd7dc3ef..bf18c42d 100644 --- a/nio-server/app/db/UserMongoDataStore.scala +++ b/nio-server/app/db/UserMongoDataStore.scala @@ -5,10 +5,11 @@ import org.apache.pekko.stream.scaladsl.Source import models._ import play.api.libs.json.{JsValue, Json, OFormat} import play.modules.reactivemongo.ReactiveMongoApi +import reactivemongo.api.indexes.Index.Default import reactivemongo.api.indexes.{Index, IndexType} import scala.concurrent.{ExecutionContext, Future} -import scala.collection.{immutable, Seq} +import scala.collection.{Seq, immutable} class UserMongoDataStore(val mongoApi: ReactiveMongoApi)(implicit val executionContext: ExecutionContext) extends MongoDataStore[User] { @@ -16,7 +17,6 @@ class UserMongoDataStore(val mongoApi: ReactiveMongoApi)(implicit val executionC import reactivemongo.api.bson._ import reactivemongo.play.json.compat._ import reactivemongo.pekkostream._ - import lax._ import bson2json._ import json2bson._ @@ -24,7 +24,7 @@ class UserMongoDataStore(val mongoApi: ReactiveMongoApi)(implicit val executionC override def collectionName(tenant: String) = s"$tenant-users" - override def indices = Seq( + override def indices: Seq[Default] = Seq( Index( key = immutable.Seq("orgKey" -> IndexType.Ascending, "userId" -> IndexType.Ascending), name = Some("orgKey_userId"), @@ -79,27 +79,6 @@ class UserMongoDataStore(val mongoApi: ReactiveMongoApi)(implicit val executionC .documentSource() } - def streamAllConsentFactIds(tenant: String)(implicit m: Materializer): Future[Source[JsValue, Future[State]]] = - storedCollection(tenant).map { col => - col - .find( - Json.obj(), - Some(Json.obj("_id" -> 0, "latestConsentFactId" -> 1)) - ) - .cursor[JsValue]() - .documentSource() - } - - def streamAllUsersConsentFacts(tenant: String)(implicit m: Materializer): Future[Source[JsValue, Future[State]]] = - storedCollection(tenant).map { users => - users - .aggregateWith[JsValue]() { framework => - import framework.Lookup - List(Lookup(s"$tenant-consentFacts", "latestConsentFactId", "_id", "consentFact")) - } - .documentSource() - } - def deleteUserByTenant(tenant: String): Future[Boolean] = storedCollection(tenant).flatMap { col => col.drop(failIfNotFound = false) diff --git a/nio-server/app/filters/Auth0Filter.scala b/nio-server/app/filters/Auth0Filter.scala index 5b188110..fad14615 100644 --- a/nio-server/app/filters/Auth0Filter.scala +++ b/nio-server/app/filters/Auth0Filter.scala @@ -4,9 +4,6 @@ import java.util.Base64 import org.apache.pekko.stream.Materializer import auth.AuthInfo -import com.auth0.jwt.JWT -import com.auth0.jwt.algorithms.Algorithm -import com.auth0.jwt.interfaces.DecodedJWT import com.google.common.base.Charsets import configuration.Env import db.ApiKeyMongoDataStore @@ -22,7 +19,7 @@ class Auth0Filter(env: Env, authInfoMock: AuthInfoMock, apiKeyMongoDataStore: Ap ec: ExecutionContext, val mat: Materializer ) extends Filter { - private val logger = Logger("filter") + Logger("filter") private lazy val auth0Config = env.config.filter.auth0 private val decoder = Base64.getDecoder @@ -74,7 +71,7 @@ class Auth0Filter(env: Env, authInfoMock: AuthInfoMock, apiKeyMongoDataStore: Ap case (_, _, _, Some((clientId, clientSecret)), _, _) => validateByApiKey(next, requestHeader, clientId, clientSecret) - case (_, Some(email), Some(token), _, _, _) => + case (_, Some(email), Some(_), _, _, _) => // TODO : récupérer les informations en session next( requestHeader.addAttr(FilterAttributes.Email, email) diff --git a/nio-server/app/filters/OtoroshiFilter.scala b/nio-server/app/filters/OtoroshiFilter.scala index 1651133a..839f0cd5 100644 --- a/nio-server/app/filters/OtoroshiFilter.scala +++ b/nio-server/app/filters/OtoroshiFilter.scala @@ -21,7 +21,7 @@ class OtoroshiFilter(env: Env, authInfoMock: AuthInfoMock)(implicit ec: Executio val config: OtoroshiFilterConfig = env.config.filter.otoroshi - private val logger = Logger("filter") + Logger("filter") override def apply(nextFilter: RequestHeader => Future[Result])(requestHeader: RequestHeader): Future[Result] = { val startTime = System.currentTimeMillis diff --git a/nio-server/app/libs/io.scala b/nio-server/app/libs/io.scala index 59673a34..f5f43540 100644 --- a/nio-server/app/libs/io.scala +++ b/nio-server/app/libs/io.scala @@ -1,9 +1,7 @@ package libs -import cats.Parallel import cats.data.EitherT import cats.implicits._ -import cats.syntax._ import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success} diff --git a/nio-server/app/libs/xml/XmlParser.scala b/nio-server/app/libs/xml/XmlParser.scala index 7f9a7919..694b7184 100644 --- a/nio-server/app/libs/xml/XmlParser.scala +++ b/nio-server/app/libs/xml/XmlParser.scala @@ -59,7 +59,7 @@ object implicits { private def toPath(path: Option[String]): String = path.map(p => s".$p").getOrElse("") - private def buildError(path: Option[String]): AppErrors = + def buildError(path: Option[String]): AppErrors = AppErrors.error(s"unknow.path${toPath(path)}") implicit def readString: XMLRead[String] = diff --git a/nio-server/app/loader/NioLoader.scala b/nio-server/app/loader/NioLoader.scala index 08663489..0b82ee5b 100644 --- a/nio-server/app/loader/NioLoader.scala +++ b/nio-server/app/loader/NioLoader.scala @@ -13,7 +13,7 @@ import filters._ import messaging.KafkaMessageBroker import play.api.ApplicationLoader.Context import play.api._ -import play.api.http.{DefaultHttpErrorHandler, HttpErrorHandler, MediaRange} +import play.api.http.{DefaultHttpErrorHandler, HttpErrorHandler} import play.api.libs.json.Json import play.api.libs.ws.ahc.AhcWSComponents import play.api.mvc.BodyParsers.Default @@ -176,7 +176,7 @@ class NioComponents(context: Context) override lazy val httpErrorHandler: HttpErrorHandler = new HttpErrorHandler { private lazy val defaultHandler = - new DefaultHttpErrorHandler(environment, configuration, sourceMapper, Some(router)) + new DefaultHttpErrorHandler(environment, configuration, devContext.map(_.sourceMapper), Some(router)) def acceptedContent(request: RequestHeader, statusCode: Int, message: String, uuid: String): Result = request.headers.get("Accept") match { diff --git a/nio-server/app/loader/Starter.scala b/nio-server/app/loader/Starter.scala index 0e785193..fe8013f1 100644 --- a/nio-server/app/loader/Starter.scala +++ b/nio-server/app/loader/Starter.scala @@ -37,7 +37,7 @@ class Starter( def initialize() = { - implicit val mat: Materializer = Materializer(system) + Materializer(system) // clean up db val dbFlush: Boolean = config.get[Boolean]("db.flush") diff --git a/nio-server/app/messaging/KafkaMessageBroker.scala b/nio-server/app/messaging/KafkaMessageBroker.scala index a721ccce..a9ca8024 100644 --- a/nio-server/app/messaging/KafkaMessageBroker.scala +++ b/nio-server/app/messaging/KafkaMessageBroker.scala @@ -3,8 +3,7 @@ package messaging import java.io.Closeable import java.security.MessageDigest import org.apache.pekko.actor.ActorSystem -import org.apache.pekko.kafka.ConsumerMessage.CommittableOffsetBatch -import org.apache.pekko.kafka.{CommitterSettings, ConsumerMessage, ProducerSettings, Subscriptions} +import org.apache.pekko.kafka.{CommitterSettings, ProducerSettings, Subscriptions} import org.apache.pekko.kafka.scaladsl.{Committer, Consumer} import org.apache.pekko.stream.Materializer import org.apache.pekko.stream.scaladsl.Keep.both @@ -12,7 +11,7 @@ import org.apache.pekko.stream.scaladsl.{Flow, Source} import org.apache.pekko.{Done, NotUsed} import configuration.{Env, KafkaConfig} import models.{Digest, NioEvent, SecuredEvent} -import org.apache.kafka.clients.producer.{Callback, KafkaProducer, Producer, ProducerRecord, RecordMetadata} +import org.apache.kafka.clients.producer.{Callback, Producer, ProducerRecord, RecordMetadata} import org.apache.kafka.common.TopicPartition import utils.NioLogger import play.api.libs.json.Json @@ -22,7 +21,7 @@ import scala.jdk.CollectionConverters._ import scala.concurrent.duration.FiniteDuration import scala.concurrent.{ExecutionContext, Future, Promise} import scala.util.control.NonFatal -import scala.collection.{Seq, immutable} +import scala.collection.Seq class KafkaMessageBroker(actorSystem: ActorSystem)(implicit context: ExecutionContext, env: Env, s3Manager: FSManager) extends Closeable { diff --git a/nio-server/app/models/Account.scala b/nio-server/app/models/Account.scala index df0523a6..8646a94c 100644 --- a/nio-server/app/models/Account.scala +++ b/nio-server/app/models/Account.scala @@ -99,7 +99,7 @@ object Account extends ReadableEntity[Account] { implicit val oformat: OFormat[Account] = OFormat(read, write) implicit val readXml: XMLRead[Account] = - (node: NodeSeq, path: Option[String]) => + (node: NodeSeq, _: Option[String]) => ( (node \ "accountId").validate[String](Some("account.accountId")), (node \ "organisationsUsers").validate[Seq[OrganisationUser]](Some("account.organisationsUsers")) diff --git a/nio-server/app/models/ApiKey.scala b/nio-server/app/models/ApiKey.scala index 217a5ce4..c27d4cae 100644 --- a/nio-server/app/models/ApiKey.scala +++ b/nio-server/app/models/ApiKey.scala @@ -134,7 +134,7 @@ object ApiKey extends ReadableEntity[ApiKey] { def fromXml(xml: Elem): Either[AppErrors, ApiKey] = readXml.read(xml, Some("ApiKey")).toEither - def fromJson(json: JsValue) = + def fromJson(json: JsValue): Either[AppErrors, ApiKey] = json.validate[ApiKey] match { case JsSuccess(o, _) => Right(o) case JsError(errors) => Left(AppErrors.fromJsError(errors)) @@ -143,7 +143,7 @@ object ApiKey extends ReadableEntity[ApiKey] { } case class ApiKeys(page: Int, pageSize: Int, count: Long, items: Seq[ApiKey]) extends ModelTransformAs { - def asJson() = + def asJson(): JsObject = Json.obj("page" -> page, "pageSize" -> pageSize, "count" -> count, "items" -> JsArray(items.map(_.asJson()))) def asXml() = diff --git a/nio-server/app/models/Auth.scala b/nio-server/app/models/Auth.scala index e2c8973c..de444415 100644 --- a/nio-server/app/models/Auth.scala +++ b/nio-server/app/models/Auth.scala @@ -11,7 +11,6 @@ import utils.Result import utils.Result.AppErrors import scala.xml.{Elem, NodeSeq} -import scala.collection.Seq case class Auth(email: String, password: String) diff --git a/nio-server/app/models/ConsentFact.scala b/nio-server/app/models/ConsentFact.scala index 0ddfbb1a..efece07e 100644 --- a/nio-server/app/models/ConsentFact.scala +++ b/nio-server/app/models/ConsentFact.scala @@ -16,7 +16,6 @@ import utils.Result.AppErrors import utils.Result.AppErrors._ import utils.json.JsResultOps -import java.time.format.DateTimeFormatter import java.time.{Clock, LocalDateTime} import scala.xml.{Elem, NodeSeq} import scala.collection.Seq @@ -36,7 +35,7 @@ object Metadata { case class DoneBy(userId: String, role: String) object DoneBy { - implicit val doneByFormats = Json.format[DoneBy] + implicit val doneByFormats: OFormat[DoneBy] = Json.format[DoneBy] implicit val xmlRead: XMLRead[DoneBy] = { import AppErrors._ @@ -54,8 +53,8 @@ object DoneBy { } } -case class Consent(key: String, label: String, checked: Boolean) { - def asXml() = +case class Consent(key: String, label: String, checked: Boolean, expiredAt: Option[LocalDateTime] = None) { + def asXml(): Elem = {key} @@ -65,25 +64,30 @@ case class Consent(key: String, label: String, checked: Boolean) { {checked} + {expiredAt.map(l => {l.format(DateUtils.utcDateFormatter)})} .clean() } object Consent { - implicit val consentFormats = Json.format[Consent] + implicit val consentFormats: OFormat[Consent] = { + implicit val dateFormat: Format[LocalDateTime] = DateUtils.utcDateTimeFormats + Json.format[Consent] + } implicit val xmlRead: XMLRead[Consent] = (xml: NodeSeq, path: Option[String]) => { ( (xml \ "key").validate[String](Some(s"${path.convert()}key")), (xml \ "label").validate[String](Some(s"${path.convert()}label")), - (xml \ "checked").validate[Boolean](Some(s"${path.convert()}checked")) + (xml \ "checked").validate[Boolean](Some(s"${path.convert()}checked")), + (xml \ "expiredAt").validateNullable[LocalDateTime](Some(s"${path.convert()}expiredAt")) ).mapN(Consent.apply) } } case class ConsentGroup(key: String, label: String, consents: Seq[Consent]) { - def asXml() = + def asXml(): Elem = {key} @@ -97,7 +101,7 @@ case class ConsentGroup(key: String, label: String, consents: Seq[Consent]) { } object ConsentGroup { - implicit val consentGroupFormats = Json.format[ConsentGroup] + implicit val consentGroupFormats: OFormat[ConsentGroup] = Json.format[ConsentGroup] implicit val xmlRead: XMLRead[ConsentGroup] = (xml: NodeSeq, path: Option[String]) => { @@ -188,25 +192,30 @@ object ConsentOffer extends ReadableEntity[ConsentOffer] { case class PartialConsent(key: String, label: Option[String], checked: Boolean) { - def toConsent: Consent = Consent(key, label.getOrElse(""), checked) + def toConsent(permission: Option[Permission]): Consent = Consent(key, label.getOrElse(""), checked, permission.flatMap(_.getValidityPeriod)) } object PartialConsent { - def merge(pcs: Seq[PartialConsent], consents: Seq[Consent]): Seq[Consent] = { + + def merge(pcs: Seq[PartialConsent], consents: Seq[Consent], permission: Seq[Permission]): Seq[Consent] = { + val keyToPermission = permission.groupBy(_.key) consents.map { c => + val mayBePermission: Option[Permission] = keyToPermission.get(c.key).flatMap(_.headOption) pcs.find(pc => pc.key == c.key).fold(c) { pc => c.copy( label = pc.label.getOrElse(c.label), - checked = pc.checked + checked = pc.checked, + expiredAt = mayBePermission.flatMap(_.getValidityPeriod) ) } } ++ pcs.filter(pc => !consents.exists(c => c.key == pc.key)).map { pc => - Consent(pc.key, pc.label.getOrElse(""), pc.checked) + val mayBePermission: Option[Permission] = keyToPermission.get(pc.key).flatMap(_.headOption) + Consent(pc.key, pc.label.getOrElse(""), pc.checked, mayBePermission.flatMap(_.getValidityPeriod)) } } - implicit val format = Json.format[PartialConsent] + implicit val format: OFormat[PartialConsent] = Json.format[PartialConsent] implicit val partialConsentReadXml: XMLRead[PartialConsent] = (node: NodeSeq, path: Option[String]) => ( @@ -219,24 +228,27 @@ case class PartialConsentGroup (key: String, label: Option[String], consents: Op object PartialConsentGroup { - def merge(partialGroups: Seq[PartialConsentGroup], existingGroups: Seq[ConsentGroup]): Seq[ConsentGroup] = { + def merge(partialGroups: Seq[PartialConsentGroup], existingGroups: Seq[ConsentGroup], permissionGroups: Seq[PermissionGroup]): Seq[ConsentGroup] = { + val permissionsByKey: Map[String, Seq[PermissionGroup]] = permissionGroups.groupBy(_.key) existingGroups.map { g => partialGroups.find(pg => pg.key == g.key).fold( g ) { pg => + val mayBePermission: Option[PermissionGroup] = permissionsByKey.get(pg.key).flatMap(_.headOption) g.copy( label = pg.label.getOrElse(g.label), - consents = pg.consents.map(pcs => PartialConsent.merge(pcs, g.consents)).getOrElse(g.consents) + consents = pg.consents.map(pcs => PartialConsent.merge(pcs, g.consents, mayBePermission.toList.flatMap(_.permissions))).getOrElse(g.consents) ) } - } ++ partialGroups.filter(pg => !existingGroups.exists(g => pg.key == g.key)).map { pc => + } ++ partialGroups.filter(pg => !existingGroups.exists(g => pg.key == g.key)).map { pcg => + val mayBePermission: List[Permission] = permissionsByKey.get(pcg.key).toList.flatMap(_.headOption).flatMap(_.permissions) ConsentGroup( - pc.key, - pc.label.getOrElse(""), - pc.consents.toList.flatten.map(pc => pc.toConsent) + pcg.key, + pcg.label.getOrElse(""), + pcg.consents.toList.flatten.map(pc => pc.toConsent(mayBePermission.find(_.key == pc.key))) ) } } - implicit val format = Json.format[PartialConsentGroup] + implicit val format: OFormat[PartialConsentGroup] = Json.format[PartialConsentGroup] implicit val partialConsentGroupReadXml: XMLRead[PartialConsentGroup] = (node: NodeSeq, path: Option[String]) => ( @@ -260,7 +272,7 @@ object PartialConsentOffer { label = po.label.getOrElse(o.label), lastUpdate = po.lastUpdate.getOrElse(o.lastUpdate), version = po.version.getOrElse(o.version), - groups = PartialConsentGroup.merge(po.groups.toList.flatten, o.groups) + groups = PartialConsentGroup.merge(po.groups.toList.flatten, o.groups, Seq.empty) ) } } @@ -273,7 +285,7 @@ object PartialConsentOffer { groups = po.groups.toList.flatten.map(pg => ConsentGroup( key = pg.key, label = pg.label.getOrElse(""), - consents = pg.consents.map(_.map(_.toConsent)).toList.flatten + consents = pg.consents.map(_.map(_.toConsent(None))).toList.flatten )) ) } @@ -281,8 +293,8 @@ object PartialConsentOffer { } } - implicit val format = { - implicit val dateRead = DateUtils.utcDateTimeFormats + implicit val format: OFormat[PartialConsentOffer] = { + implicit val dateRead: Format[LocalDateTime] = DateUtils.utcDateTimeFormats Json.format[PartialConsentOffer] } @@ -307,15 +319,15 @@ case class PartialConsentFact( orgKey: Option[String] = None, metaData: Option[Map[String, String]] = None, sendToKafka: Option[Boolean] = None) { - def applyTo(lastConsentFact: ConsentFact, currentVersion: VersionInfo): ConsentFact = { + def applyTo(lastConsentFact: ConsentFact, organisation: Organisation): ConsentFact = { val finalConsent = lastConsentFact.copy( _id = BSONObjectID.generate().stringify, userId = userId.getOrElse(lastConsentFact.userId), doneBy = doneBy.getOrElse(lastConsentFact.doneBy), - version = version.getOrElse(currentVersion.num), + version = version.getOrElse(organisation.version.num), lastUpdate = lastUpdate.getOrElse(lastConsentFact.lastUpdate), lastUpdateSystem = LocalDateTime.now(Clock.systemUTC), - groups = groups.map(g => PartialConsentGroup.merge(g, lastConsentFact.groups)).getOrElse(lastConsentFact.groups), + groups = groups.map(g => PartialConsentGroup.merge(g, lastConsentFact.groups, organisation.groups)).getOrElse(lastConsentFact.groups), offers = offers.map(o => PartialConsentOffer.merge(o, lastConsentFact.offers)).getOrElse(lastConsentFact.offers), orgKey = orgKey.orElse(lastConsentFact.orgKey), metaData = metaData.map(meta => lastConsentFact.metaData.map(m => m.combine(meta)).getOrElse(meta)).orElse(lastConsentFact.metaData), @@ -328,8 +340,8 @@ case class PartialConsentFact( object PartialConsentFact extends ReadableEntity[PartialConsentFact] { - implicit val format = { - implicit val dateRead = DateUtils.utcDateTimeFormats + implicit val format: OFormat[PartialConsentFact] = { + implicit val dateRead: Format[LocalDateTime] = DateUtils.utcDateTimeFormats Json.format[PartialConsentFact] } @@ -381,10 +393,10 @@ case class ConsentFact( sendToKafka: Option[Boolean] = None ) extends ModelTransformAs { - def notYetSendToKafka() = this.copy(sendToKafka = Some(false)) - def nowSendToKafka() = this.copy(sendToKafka = Some(true)) + def notYetSendToKafka(): ConsentFact = this.copy(sendToKafka = Some(false)) + def nowSendToKafka(): ConsentFact = this.copy(sendToKafka = Some(true)) - def asJson() = + def asJson(): JsValue = transform(ConsentFact.consentFactWritesWithoutId.writes(this)) private def transform(jsValue: JsValue): JsValue = @@ -436,6 +448,39 @@ case class ConsentFact( }.get } .clean() + + case class KeyPermissionGroup(group: String, permission: String) + + def setUpValidityPeriods(organisation: Organisation): ConsentFact = { + val indexedKeys: Seq[(KeyPermissionGroup, Permission)] = for { + g <- organisation.groups + p <- g.permissions + } yield (KeyPermissionGroup(g.key, p.key), p) + val indexed: Map[KeyPermissionGroup, Seq[(KeyPermissionGroup, Permission)]] = indexedKeys.groupBy(_._1) + this.copy(groups = this.groups.map( group => + group.copy( + consents = group.consents.map ( consent => + consent.copy(expiredAt = indexed.get(KeyPermissionGroup(group.key, consent.key)) + .flatMap(_.headOption) + .flatMap(_._2.getValidityPeriod) + ) + ) + ) + )) + } + + def filterExpiredConsent(showExpiredConsents: Boolean): ConsentFact = { + if (showExpiredConsents) { + this + } else { + val now = LocalDateTime.now(Clock.systemUTC()) + this.copy(groups = this.groups.map(group => + group.copy(consents = group.consents.toList.filter(c => + c.expiredAt.isEmpty || c.expiredAt.exists(d => d.isAfter(now)) + )) + )) + } + } } object ConsentFact extends ReadableEntity[ConsentFact] { @@ -449,7 +494,7 @@ object ConsentFact extends ReadableEntity[ConsentFact] { orgKey: Option[String] = None, metaData: Option[Map[String, String]] = None, sendToKafka: Option[Boolean] = None - ) = + ): ConsentFact = ConsentFact( _id = BSONObjectID.generate().stringify, userId = userId, @@ -473,7 +518,7 @@ object ConsentFact extends ReadableEntity[ConsentFact] { lastUpdateSystem: LocalDateTime = LocalDateTime.now(Clock.systemUTC), orgKey: Option[String] = None, metaData: Option[Map[String, String]] = None - ) = + ): ConsentFact = ConsentFact( _id = _id, userId = userId, @@ -499,7 +544,7 @@ object ConsentFact extends ReadableEntity[ConsentFact] { (__ \ "sendToKafka").readNullable[Boolean] )(ConsentFact.newWithoutIdAndLastUpdate _) - val consentFactReads: Reads[ConsentFact] = ( + private val consentFactReads: Reads[ConsentFact] = ( (__ \ "_id").read[String] and (__ \ "userId").read[String] and (__ \ "doneBy").read[DoneBy] and @@ -512,61 +557,52 @@ object ConsentFact extends ReadableEntity[ConsentFact] { (__ \ "metaData").readNullable[Map[String, String]] )(ConsentFact.newWithoutKafkaFlag _) - val consentFactWrites: Writes[ConsentFact] = ( + private val consentFactWrites: Writes[ConsentFact] = ( (JsPath \ "_id").write[String] and (JsPath \ "userId").write[String] and (JsPath \ "doneBy").write[DoneBy](DoneBy.doneByFormats) and (JsPath \ "version").write[Int] and (JsPath \ "groups").write[Seq[ConsentGroup]] and (JsPath \ "offers").writeNullable[Seq[ConsentOffer]] and - (JsPath \ "lastUpdate") - .write[LocalDateTime](DateUtils.utcDateTimeWrites) and - (JsPath \ "lastUpdateSystem") - .write[LocalDateTime](DateUtils.utcDateTimeWrites) and + (JsPath \ "lastUpdate").write[LocalDateTime](DateUtils.utcDateTimeWrites) and + (JsPath \ "lastUpdateSystem").write[LocalDateTime](DateUtils.utcDateTimeWrites) and (JsPath \ "orgKey").writeNullable[String] and (JsPath \ "metaData").writeNullable[Map[String, String]] and (JsPath \ "sendToKafka").writeNullable[Boolean] )(unlift(ConsentFact.unapply)) - val consentFactWritesWithoutId: Writes[ConsentFact] = ( + private val consentFactWritesWithoutId: Writes[ConsentFact] = ( (JsPath \ "_id").writeNullable[String].contramap((_: String) => None) and (JsPath \ "userId").write[String] and (JsPath \ "doneBy").write[DoneBy](DoneBy.doneByFormats) and (JsPath \ "version").write[Int] and (JsPath \ "groups").write[Seq[ConsentGroup]] and (JsPath \ "offers").writeNullable[Seq[ConsentOffer]] and - (JsPath \ "lastUpdate") - .write[LocalDateTime](DateUtils.utcDateTimeWrites) and - (JsPath \ "lastUpdateSystem") - .writeNullable[LocalDateTime](DateUtils.utcDateTimeWrites) - .contramap((_: LocalDateTime ) => None) and + (JsPath \ "lastUpdate").write[LocalDateTime](DateUtils.utcDateTimeWrites) and + (JsPath \ "lastUpdateSystem").writeNullable[LocalDateTime](DateUtils.utcDateTimeWrites).contramap((_: LocalDateTime ) => None) and (JsPath \ "orgKey").writeNullable[String] and (JsPath \ "metaData").writeNullable[Map[String, String]] and (JsPath \ "sendToKafka").writeNullable[Boolean] )(unlift(ConsentFact.unapply)) - val consentFactOWrites: OWrites[ConsentFact] = ( + private val consentFactOWrites: OWrites[ConsentFact] = ( (JsPath \ "_id").write[String] and (JsPath \ "userId").write[String] and (JsPath \ "doneBy").write[DoneBy](DoneBy.doneByFormats) and (JsPath \ "version").write[Int] and (JsPath \ "groups").write[Seq[ConsentGroup]] and (JsPath \ "offers").writeNullable[Seq[ConsentOffer]] and - (JsPath \ "lastUpdate") - .write[LocalDateTime](DateUtils.utcDateTimeWrites) and - (JsPath \ "lastUpdateSystem") - .write[LocalDateTime](DateUtils.utcDateTimeWrites) and + (JsPath \ "lastUpdate").write[LocalDateTime](DateUtils.utcDateTimeWrites) and + (JsPath \ "lastUpdateSystem").write[LocalDateTime](DateUtils.utcDateTimeWrites) and (JsPath \ "orgKey").writeNullable[String] and (JsPath \ "metaData").writeNullable[Map[String, String]] and (JsPath \ "sendToKafka").writeNullable[Boolean] )(unlift(ConsentFact.unapply)) - val consentFactFormats: Format[ConsentFact] = - Format(consentFactReads, consentFactWrites) - implicit val consentFactOFormats: OFormat[ConsentFact] = - OFormat(consentFactReads, consentFactOWrites) + val consentFactFormats: Format[ConsentFact] = Format(consentFactReads, consentFactWrites) + implicit val consentFactOFormats: OFormat[ConsentFact] = OFormat(consentFactReads, consentFactOWrites) - def template(orgVerNum: Int, groups: Seq[ConsentGroup], offers: Option[Seq[ConsentOffer]] = None, orgKey: String) = + def template(orgVerNum: Int, groups: Seq[ConsentGroup], offers: Option[Seq[ConsentOffer]] = None, orgKey: String): ConsentFact = ConsentFact( _id = null, userId = "fill", @@ -625,19 +661,19 @@ object ConsentFactCommand { case class PatchConsentFact(userId: String, command: PartialConsentFact) extends ConsentFactCommand object PatchConsentFact { - val format = Json.format[PatchConsentFact] + val format: OFormat[PatchConsentFact] = Json.format[PatchConsentFact] } case class UpdateConsentFact(userId: String, command: ConsentFact) extends ConsentFactCommand object UpdateConsentFact { - val format = OFormat[UpdateConsentFact]( + val format: OFormat[UpdateConsentFact] = OFormat[UpdateConsentFact]( ((__ \ "userId").read[String] and (__ \ "command").read[ConsentFact](ConsentFact.consentFactReadsWithoutIdAndLastUpdate))(UpdateConsentFact.apply _), Json.writes[UpdateConsentFact] ) } - implicit val format = Format( + implicit val format: Format[ConsentFactCommand] = Format( Reads[ConsentFactCommand] { js => (js \ "type").validate[String].flatMap { case "Update" => UpdateConsentFact.format.reads(js) diff --git a/nio-server/app/models/DeletionTask.scala b/nio-server/app/models/DeletionTask.scala index b5e6803c..7d5c4f0d 100644 --- a/nio-server/app/models/DeletionTask.scala +++ b/nio-server/app/models/DeletionTask.scala @@ -1,12 +1,15 @@ package models import models.DeletionTaskStatus.DeletionTaskStatus -import java.time.{LocalDateTime, Clock} + +import java.time.{Clock, LocalDateTime} import play.api.libs.json._ import reactivemongo.api.bson.BSONObjectID import utils.DateUtils import libs.xml.XmlUtil.XmlCleaner + import scala.collection.Seq +import scala.xml.Elem object DeletionTaskStatus extends Enumeration { type DeletionTaskStatus = Value @@ -15,27 +18,24 @@ object DeletionTaskStatus extends Enumeration { def from(name: String): Value = values.find(_.toString.toLowerCase == name.toLowerCase()).getOrElse(Unknown) - implicit val deletionTaskStatusReads = new Reads[DeletionTaskStatus] { - def reads(json: JsValue) = - JsSuccess(DeletionTaskStatus.withName(json.as[String])) - } + implicit val deletionTaskStatusReads: Reads[DeletionTaskStatus] = (json: JsValue) => JsSuccess(DeletionTaskStatus.withName(json.as[String])) } case class AppDeletionState(appId: String, status: DeletionTaskStatus) { - def asJson() = Json.obj("appId" -> appId, "status" -> status.toString) + def asJson(): JsObject = Json.obj("appId" -> appId, "status" -> status.toString) - def asXml() = + def asXml(): Elem = {appId} {status.toString} .clean() } object AppDeletionState { - implicit val appDeletionStateFormats = Json.format[AppDeletionState] + implicit val appDeletionStateFormats: OFormat[AppDeletionState] = Json.format[AppDeletionState] } case class DeletionTaskInfoPerApp(orgKey: String, userId: String, appId: String, deletionTaskId: String) { - def asJson() = Json.obj( + def asJson(): JsObject = Json.obj( "orgKey" -> orgKey, "userId" -> userId, "appId" -> appId, @@ -54,7 +54,7 @@ case class DeletionTask( lastUpdate: LocalDateTime ) extends ModelTransformAs { - def copyWithAppDone(appId: String) = { + def copyWithAppDone(appId: String): DeletionTask = { val newStates = states.filterNot(_.appId == appId) + AppDeletionState(appId, DeletionTaskStatus.Done) if (newStates.forall(_.status == DeletionTaskStatus.Done)) { this.copy(states = newStates, status = DeletionTaskStatus.Done) @@ -63,7 +63,7 @@ case class DeletionTask( } } - def asJson() = Json.obj( + def asJson(): JsObject = Json.obj( "id" -> _id, "orgKey" -> orgKey, "userId" -> userId, @@ -74,11 +74,11 @@ case class DeletionTask( "lastUpdate" -> startedAt.format(DateUtils.utcDateFormatter) ) - def asXml() = + def asXml(): Elem = {orgKey} {userId} {startedAt.format(DateUtils.utcDateFormatter)} - {appIds.map(appId => appId)} + {appIds.map(_ => appId)} {states.map(_.asXml())} {status.toString} {lastUpdate.format(DateUtils.utcDateFormatter)} @@ -87,10 +87,10 @@ case class DeletionTask( object DeletionTask { - implicit val dateTimeFormats = DateUtils.utcDateTimeFormats - implicit val deletionTaskFormats = Json.format[DeletionTask] + implicit val dateTimeFormats: Format[LocalDateTime] = DateUtils.utcDateTimeFormats + implicit val deletionTaskFormats: OFormat[DeletionTask] = Json.format[DeletionTask] - def newTask(orgKey: String, userId: String, appIds: Set[String]) = { + def newTask(orgKey: String, userId: String, appIds: Set[String]): DeletionTask = { val now = LocalDateTime.now(Clock.systemUTC) DeletionTask( BSONObjectID.generate().stringify, @@ -108,10 +108,10 @@ object DeletionTask { case class PagedDeletionTasks(page: Int, pageSize: Int, count: Long, items: Seq[DeletionTask]) extends ModelTransformAs { - def asJson() = + def asJson(): JsObject = Json.obj("page" -> page, "pageSize" -> pageSize, "count" -> count, "items" -> JsArray(items.map(_.asJson()))) - def asXml() = + def asXml(): Elem = {page} {pageSize} {count} diff --git a/nio-server/app/models/ExtractionTask.scala b/nio-server/app/models/ExtractionTask.scala index 7c421261..8d8184f8 100644 --- a/nio-server/app/models/ExtractionTask.scala +++ b/nio-server/app/models/ExtractionTask.scala @@ -11,14 +11,15 @@ import libs.xml.implicits._ import libs.xml.syntax._ import messaging.KafkaMessageBroker import models.ExtractionTaskStatus.ExtractionTaskStatus -import java.time.{LocalDateTime, Clock} + +import java.time.{Clock, LocalDateTime} import play.api.libs.json.Reads._ import play.api.libs.json._ import reactivemongo.api.bson.BSONObjectID import utils.Result.AppErrors import utils.{DateUtils, UploadTracker} -import scala.concurrent.ExecutionContext +import scala.concurrent.{ExecutionContext, Future} import scala.xml.{Elem, NodeSeq} import scala.collection.Seq @@ -43,20 +44,20 @@ object ExtractionTaskStatus extends Enumeration { case _ => Unknown } - implicit val extractionTaskStatusFormat = Format[ExtractionTaskStatus]( + implicit val extractionTaskStatusFormat: Format[ExtractionTaskStatus] = Format[ExtractionTaskStatus]( __.read[String].map(from), Writes[ExtractionTaskStatus](s => JsString(s.show)) ) } case class FileMetadata(name: String, contentType: String, size: Long) { - def asJson() = Json.obj( + def asJson(): JsObject = Json.obj( "name" -> name, "contentType" -> contentType, "size" -> size ) - def asXml() = + def asXml(): Elem = {name} {contentType} {size} @@ -64,7 +65,7 @@ case class FileMetadata(name: String, contentType: String, size: Long) { } object FileMetadata { - implicit val fileMetadataFormats = Json.format[FileMetadata] + implicit val fileMetadataFormats: OFormat[FileMetadata] = Json.format[FileMetadata] implicit val readXml: XMLRead[FileMetadata] = (node: NodeSeq, path: Option[String]) => @@ -76,19 +77,19 @@ object FileMetadata { } case class FilesMetadata(files: Seq[FileMetadata]) { - def asJson() = FilesMetadata.filesMetadataFormats.writes(this) + def asJson(): JsObject = FilesMetadata.filesMetadataFormats.writes(this) - def asXml() = + def asXml(): Elem = {files.map(_.asXml())} .clean() } object FilesMetadata extends ReadableEntity[FilesMetadata] { - implicit val filesMetadataFormats = Json.format[FilesMetadata] + implicit val filesMetadataFormats: OFormat[FilesMetadata] = Json.format[FilesMetadata] implicit val readXml: XMLRead[FilesMetadata] = - (node: NodeSeq, path: Option[String]) => node.validate[Seq[FileMetadata]].map(files => FilesMetadata(files)) + (node: NodeSeq, _: Option[String]) => node.validate[Seq[FileMetadata]].map(files => FilesMetadata(files)) def fromXml(xml: Elem): Either[AppErrors, FilesMetadata] = readXml.read(xml, Some("filesMetadata")).toEither @@ -102,10 +103,10 @@ object FilesMetadata extends ReadableEntity[FilesMetadata] { case class AppState(appId: String, files: Seq[FileMetadata], totalBytes: Long, status: ExtractionTaskStatus) { - def uploadedBytes(taskId: String) = + def uploadedBytes(taskId: String): Long = UploadTracker.getUploadedBytes(taskId, appId) - def progress(taskId: String) = { + def progress(taskId: String): Long = { val bytes = uploadedBytes(taskId) if (bytes == 0L) { 0L @@ -114,9 +115,9 @@ case class AppState(appId: String, files: Seq[FileMetadata], totalBytes: Long, s } } - def asJson() = AppState.appStateFormats.writes(this) + def asJson(): JsObject = AppState.appStateFormats.writes(this) - def asXml() = + def asXml(): Elem = {appId} {files.map(_.asXml())} {totalBytes} @@ -125,7 +126,7 @@ case class AppState(appId: String, files: Seq[FileMetadata], totalBytes: Long, s } object AppState { - implicit val appStateFormats = { + implicit val appStateFormats: OFormat[AppState] = { import models.ExtractionTaskStatus._ Json.format[AppState] } @@ -145,7 +146,7 @@ case class ExtractionTask( ) extends ModelTransformAs { // FIXME - def asJson() = Json.obj( + def asJson(): JsObject = Json.obj( "id" -> _id, "orgKey" -> orgKey, "userId" -> userId, @@ -158,7 +159,7 @@ case class ExtractionTask( "done" -> done ) - def asXml() = + def asXml(): Elem = {_id} {orgKey} {userId} @@ -182,7 +183,7 @@ case class ExtractionTask( } } - def allFilesDone(appId: String) = + def allFilesDone(appId: String): Boolean = states .collectFirst { case x if x.appId == appId => x.uploadedBytes(this._id) == x.totalBytes @@ -200,7 +201,7 @@ case class ExtractionTask( copy(states = states.filterNot(_.appId == appId) + newAppState, lastUpdate = LocalDateTime.now(Clock.systemUTC)) } - def copyWithFileUploadHandled(appId: String, appState: AppState) = { + def copyWithFileUploadHandled(appId: String, appState: AppState): ExtractionTask = { // All files are uploaded for this app val newAppState = appState.copy(status = ExtractionTaskStatus.Done) val allWillBeDone = this.done + 1 == appIds.size @@ -219,7 +220,7 @@ case class ExtractionTask( ec: ExecutionContext, store: ExtractionTaskMongoDataStore, broker: KafkaMessageBroker - ) = + ): Future[Any] = // Store updated task then emit events store.updateById(tenant, this._id, this).map { _ => broker.publish( @@ -237,12 +238,12 @@ case class ExtractionTask( } } - def expire(tenant: String)(implicit ec: ExecutionContext, store: ExtractionTaskMongoDataStore) = + def expire(tenant: String)(implicit ec: ExecutionContext, store: ExtractionTaskMongoDataStore): Future[Boolean] = store.updateById(tenant, this._id, this.copy(status = ExtractionTaskStatus.Expired)) } case class ExtractionTaskInfoPerApp(orgKey: String, userId: String, appId: String, extractionTaskId: String) { - def asJson() = Json.obj( + def asJson(): JsObject = Json.obj( "orgKey" -> orgKey, "userId" -> userId, "appId" -> appId, @@ -252,11 +253,11 @@ case class ExtractionTaskInfoPerApp(orgKey: String, userId: String, appId: Strin object ExtractionTask { - implicit val dateFormats = DateUtils.utcDateTimeFormats + implicit val dateFormats: Format[LocalDateTime] = DateUtils.utcDateTimeFormats - implicit val fmt = Json.format[ExtractionTask] + implicit val fmt: OFormat[ExtractionTask] = Json.format[ExtractionTask] - def newFrom(orgKey: String, userId: String, appIds: Set[String]) = { + def newFrom(orgKey: String, userId: String, appIds: Set[String]): ExtractionTask = { val now = LocalDateTime.now(Clock.systemUTC) ExtractionTask( _id = BSONObjectID.generate().stringify, @@ -274,10 +275,10 @@ object ExtractionTask { case class PagedExtractionTasks(page: Int, pageSize: Int, count: Long, items: Seq[ExtractionTask]) extends ModelTransformAs { - def asJson() = + def asJson(): JsObject = Json.obj("page" -> page, "pageSize" -> pageSize, "count" -> count, "items" -> JsArray(items.map(_.asJson()))) - def asXml() = + def asXml(): Elem = {page} {pageSize} diff --git a/nio-server/app/models/NioAccount.scala b/nio-server/app/models/NioAccount.scala index 1b05f4d1..c0fcf142 100644 --- a/nio-server/app/models/NioAccount.scala +++ b/nio-server/app/models/NioAccount.scala @@ -160,10 +160,10 @@ object NioAccount extends ReadableEntity[NioAccount] { } case class NioAccounts(page: Int, pageSize: Int, count: Long, items: Seq[NioAccount]) extends ModelTransformAs { - def asJson() = + def asJson(): JsObject = Json.obj("page" -> page, "pageSize" -> pageSize, "count" -> count, "items" -> JsArray(items.map(_.asJson()))) - def asXml() = + def asXml(): Elem = {page} diff --git a/nio-server/app/models/Organisation.scala b/nio-server/app/models/Organisation.scala index 87403d4e..e518e52b 100644 --- a/nio-server/app/models/Organisation.scala +++ b/nio-server/app/models/Organisation.scala @@ -1,6 +1,5 @@ package models -import cats.Monoid import cats.data.Validated._ import cats.data.ValidatedNel import cats.implicits._ @@ -13,7 +12,7 @@ import java.time.{LocalDateTime, Clock} import play.api.libs.functional.syntax.{unlift, _} import play.api.libs.json.Reads._ import play.api.libs.json._ -import reactivemongo.api.bson.{BSONObjectID, maxKey} +import reactivemongo.api.bson.BSONObjectID import utils.DateUtils import utils.Result.{AppErrors, ErrorMessage, Result} @@ -27,7 +26,7 @@ case class VersionInfo( neverReleased: Option[Boolean] = Some(true), lastUpdate: LocalDateTime = LocalDateTime.now(Clock.systemUTC) ) { - def copyUpdated = copy(lastUpdate = LocalDateTime.now(Clock.systemUTC)) + def copyUpdated: VersionInfo = copy(lastUpdate = LocalDateTime.now(Clock.systemUTC)) } object VersionInfo { @@ -40,8 +39,8 @@ object VersionInfo { "lastUpdate" -> versionInfo.lastUpdate.format(DateUtils.utcDateFormatter) ) } - implicit val utcDateTimeFormats = DateUtils.utcDateTimeFormats - implicit val formats = Json.format[VersionInfo] + implicit val utcDateTimeFormats: Format[LocalDateTime] = DateUtils.utcDateTimeFormats + implicit val formats: OFormat[VersionInfo] = Json.format[VersionInfo] implicit val readXml: XMLRead[VersionInfo] = (node: NodeSeq, path: Option[String]) => @@ -65,9 +64,9 @@ case class Organisation( offers: Option[Seq[Offer]] = None ) extends ModelTransformAs { - def asJson() = Organisation.organisationWritesWithoutId.writes(this) + def asJson(): JsValue = Organisation.organisationWritesWithoutId.writes(this) - def asXml() = + def asXml(): Elem = {key} @@ -189,7 +188,7 @@ object OrganisationDraft extends ReadableEntity[Organisation] { def fromXml(xml: Elem): Either[AppErrors, Organisation] = readXml.read(xml, Some("organisation")).toEither - def fromJson(json: JsValue) = + def fromJson(json: JsValue): Either[AppErrors, Organisation] = json.validate[Organisation] match { case JsSuccess(o, _) => Right(o) case JsError(errors) => Left(AppErrors.fromJsError(errors)) @@ -245,7 +244,7 @@ object Organisation extends ReadableEntity[Organisation] { org.offers match { case Some(offers) if offers.isEmpty => organisation - case Some(offers) => + case Some(_) => organisation ++ Json.obj("offers" -> org.offers.get.map(_.asJson())) case None => organisation @@ -268,7 +267,7 @@ object Organisation extends ReadableEntity[Organisation] { def fromXml(xml: Elem): Either[AppErrors, Organisation] = readXml.read(xml, Some("organisation")).toEither - def fromJson(json: JsValue) = + def fromJson(json: JsValue): Either[AppErrors, Organisation] = json.validate[Organisation] match { case JsSuccess(o, _) => Right(o) case JsError(errors) => Left(AppErrors.fromJsError(errors)) @@ -289,7 +288,7 @@ object Organisations {} case class VersionInfoLight(status: String, num: Int, lastUpdate: LocalDateTime) case class OrganisationLight(key: String, label: String, version: VersionInfoLight) { - def asXml() = + def asXml(): Elem = {key} @@ -309,7 +308,7 @@ case class OrganisationLight(key: String, label: String, version: VersionInfoLig .clean() - def asJson() = + def asJson(): JsObject = Json.obj( "key" -> key, "label" -> label, @@ -322,7 +321,7 @@ case class OrganisationLight(key: String, label: String, version: VersionInfoLig } object OrganisationLight { - def from(o: Organisation) = + def from(o: Organisation): OrganisationLight = OrganisationLight( key = o.key, label = o.label, diff --git a/nio-server/app/models/PagedConsentFacts.scala b/nio-server/app/models/PagedConsentFacts.scala index 5c37f4d4..32398f69 100644 --- a/nio-server/app/models/PagedConsentFacts.scala +++ b/nio-server/app/models/PagedConsentFacts.scala @@ -1,14 +1,16 @@ package models -import play.api.libs.json.{JsArray, Json} +import play.api.libs.json.{JsArray, JsObject, Json} + import scala.collection.Seq +import scala.xml.Elem case class PagedConsentFacts(page: Int, pageSize: Int, count: Long, items: Seq[ConsentFact]) extends ModelTransformAs { - def asJson() = + def asJson(): JsObject = Json.obj("page" -> page, "pageSize" -> pageSize, "count" -> count, "items" -> JsArray(items.map(_.asJson()))) - def asXml() = + def asXml(): Elem = {page} {pageSize} {count} diff --git a/nio-server/app/models/PagedUsers.scala b/nio-server/app/models/PagedUsers.scala index 8ba8c0f3..394c10fe 100644 --- a/nio-server/app/models/PagedUsers.scala +++ b/nio-server/app/models/PagedUsers.scala @@ -1,12 +1,14 @@ package models -import play.api.libs.json.{JsArray, Json} +import play.api.libs.json.{JsArray, JsObject, Json} import libs.xml.XmlUtil.XmlCleaner + import scala.collection.Seq +import scala.xml.Elem case class PagedUsers(page: Int, pageSize: Int, count: Long, items: Seq[User]) extends ModelTransformAs { - def asJson() = + def asJson(): JsObject = Json.obj( "page" -> page, "pageSize" -> pageSize, @@ -14,7 +16,7 @@ case class PagedUsers(page: Int, pageSize: Int, count: Long, items: Seq[User]) e "items" -> JsArray(items.map(u => Json.obj("userId" -> u.userId, "orgKey" -> u.orgKey))) ) - def asXml() = + def asXml(): Elem = {page} {pageSize} {count} diff --git a/nio-server/app/models/Permission.scala b/nio-server/app/models/Permission.scala index 6bc51361..a40aa82e 100644 --- a/nio-server/app/models/Permission.scala +++ b/nio-server/app/models/Permission.scala @@ -7,13 +7,15 @@ import libs.xml.XMLRead import libs.xml.XmlUtil.XmlCleaner import libs.xml.implicits._ import libs.xml.syntax._ -import play.api.libs._ -import play.api.libs.functional.syntax.{unlift, _} +import play.api.libs.functional.syntax._ import play.api.libs.json._ import play.api.libs.json.Reads._ import utils.Result.AppErrors -import scala.xml.NodeSeq +import java.time.{Clock, LocalDateTime, ZoneId} +import scala.concurrent.duration.{Duration, FiniteDuration} +import scala.util.{Failure, Success, Try} +import scala.xml.{Elem, NodeSeq} sealed trait PermissionType { def print : String = this match { @@ -33,7 +35,7 @@ object PermissionType { case other => Invalid(AppErrors.error(s"Unknown PermissionType $other")) } - implicit val formats = Format[PermissionType]( + implicit val formats: Format[PermissionType] = Format[PermissionType]( Reads[PermissionType] { case JsString("OptIn") => JsSuccess(OptIn) case JsString("OptOut") => JsSuccess(OptOut) @@ -53,25 +55,57 @@ object PermissionType { } } -case class Permission(key: String, label: String, `type`: PermissionType = OptIn) { +case class Permission(key: String, label: String, `type`: PermissionType = OptIn, validityPeriod: Option[FiniteDuration] = None) { + + def getValidityPeriod: Option[LocalDateTime] = validityPeriod.map(fd => LocalDateTime.now(Clock.systemUTC()).plusMinutes(fd.toMinutes).withNano(0)) + def checkDefault(): Boolean = `type` match { case OptIn => false case OptOut => true } - def asXml() = + def asXml(): Elem = {key} {`type`.print} + {validityPeriod.map(p => p.toString())} .clean() } object Permission { - implicit val formats = Format[Permission]( + + implicit val formatFiniteDuration: Format[FiniteDuration] = + Format[FiniteDuration]( + Reads[FiniteDuration] { + case JsString(str) => Try (Duration(str)) match { + case Success(value: FiniteDuration) => JsSuccess(value) + case Success(_) => JsError("invalid duration") + case Failure(_) => JsError("invalid duration") + } + case _ => JsError("invalid duration") + }, + Writes[FiniteDuration] { d => + JsString(d.toString()) + } + ) + + implicit val readXmlFiniteDuration: XMLRead[FiniteDuration] = (xml: NodeSeq, path: Option[String]) => + Try(xml.head.text) + .map { (str: String) => + Try (Duration(str)) match { + case Success(value: FiniteDuration) => value.valid + case Success(_) => AppErrors.error("invalid duration").invalid + case Failure(_) => AppErrors.error("invalid duration").invalid + } + } + .getOrElse(buildError(path).invalid) + + implicit val formats: Format[Permission] = Format[Permission]( ( (__ \ "key").read[String] and (__ \ "label").read[String] and - (__ \ "type").read[PermissionType].orElse(Reads.pure(OptIn)) + (__ \ "type").read[PermissionType].orElse(Reads.pure(OptIn)) and + (__ \ "validityPeriod").readNullable[FiniteDuration] )(Permission.apply _), Json.writes[Permission] ) @@ -81,7 +115,8 @@ object Permission { ( (node \ "key").validate[String](Some(s"${path.convert()}key")), (node \ "label").validate[String](Some(s"${path.convert()}label")), - (node \ "type").validateNullable[PermissionType](OptIn, Some(s"${path.convert()}type")) + (node \ "type").validateNullable[PermissionType](OptIn, Some(s"${path.convert()}type")), + (node \ "validityPeriod").validateNullable[FiniteDuration] ).mapN(Permission.apply) } diff --git a/nio-server/app/models/PermissionGroup.scala b/nio-server/app/models/PermissionGroup.scala index 09980f1b..e50756b0 100644 --- a/nio-server/app/models/PermissionGroup.scala +++ b/nio-server/app/models/PermissionGroup.scala @@ -6,13 +6,13 @@ import libs.xml.XMLRead import libs.xml.XmlUtil.XmlCleaner import libs.xml.implicits._ import libs.xml.syntax._ -import play.api.libs.json.Json -import scala.collection.Seq +import play.api.libs.json.{Json, OFormat} -import scala.xml.NodeSeq +import scala.collection.Seq +import scala.xml.{Elem, NodeSeq} case class PermissionGroup(key: String, label: String, permissions: Seq[Permission]) { - def asXml() = + def asXml(): Elem = {key} {permissions.map(_.asXml())} @@ -20,7 +20,7 @@ case class PermissionGroup(key: String, label: String, permissions: Seq[Permissi } object PermissionGroup { - implicit val json = Json.format[PermissionGroup] + implicit val json: OFormat[PermissionGroup] = Json.format[PermissionGroup] implicit val readXml: XMLRead[PermissionGroup] = (node: NodeSeq, path: Option[String]) => diff --git a/nio-server/app/models/Tenant.scala b/nio-server/app/models/Tenant.scala index 1a561713..70718769 100644 --- a/nio-server/app/models/Tenant.scala +++ b/nio-server/app/models/Tenant.scala @@ -14,15 +14,15 @@ import scala.xml.{Elem, NodeSeq} import scala.collection.Seq case class Tenant(key: String, description: String) extends ModelTransformAs { - def asJson() = Tenant.tenantFormats.writes(this) - def asXml() = + def asJson(): JsObject = Tenant.tenantFormats.writes(this) + def asXml(): Elem = {key} {description} .clean() } object Tenant extends ReadableEntity[Tenant] { - implicit val tenantFormats = Json.format[Tenant] + implicit val tenantFormats: OFormat[Tenant] = Json.format[Tenant] implicit val readXml: XMLRead[Tenant] = (node: NodeSeq, path: Option[String]) => @@ -31,7 +31,7 @@ object Tenant extends ReadableEntity[Tenant] { (node \ "description").validate[String](Some(s"${path.convert()}description")) ).mapN(Tenant.apply) - def fromXml(xml: Elem) = + def fromXml(xml: Elem): Either[AppErrors, Tenant] = readXml.read(xml, Some("tenant")).toEither def fromJson(json: JsValue): Either[AppErrors, Tenant] = diff --git a/nio-server/app/models/User.scala b/nio-server/app/models/User.scala index c7376637..29916fb8 100644 --- a/nio-server/app/models/User.scala +++ b/nio-server/app/models/User.scala @@ -1,8 +1,8 @@ package models -import play.api.libs.json.Json +import play.api.libs.json.{Json, OFormat} import reactivemongo.api.bson.BSONObjectID -import scala.collection.Seq + case class User( _id: String = BSONObjectID.generate().stringify, @@ -13,5 +13,5 @@ case class User( ) object User { - implicit val formats = Json.format[User] + implicit val formats: OFormat[User] = Json.format[User] } diff --git a/nio-server/app/models/UserExtract.scala b/nio-server/app/models/UserExtract.scala index e3764647..937e5693 100644 --- a/nio-server/app/models/UserExtract.scala +++ b/nio-server/app/models/UserExtract.scala @@ -7,7 +7,6 @@ import libs.xml.XmlUtil.XmlCleaner import play.api.libs.json.Reads._ import play.api.libs.json._ import utils.Result.AppErrors -import scala.collection.Seq import scala.xml.{Elem, NodeSeq} @@ -32,7 +31,7 @@ object UserExtract extends ReadableEntity[UserExtract] { Format(userExtractRead, userExtractWrite) implicit val userExtractReadXml: XMLRead[UserExtract] = - (node: NodeSeq, path: Option[String]) => (node \ "email").validate[String].map(email => UserExtract(email)) + (node: NodeSeq, _: Option[String]) => (node \ "email").validate[String].map(email => UserExtract(email)) override def fromXml(xml: Elem): Either[AppErrors, UserExtract] = userExtractReadXml.read(xml, Some("userExtract")).toEither diff --git a/nio-server/app/models/UserExtractTask.scala b/nio-server/app/models/UserExtractTask.scala index f218941d..671eca7f 100644 --- a/nio-server/app/models/UserExtractTask.scala +++ b/nio-server/app/models/UserExtractTask.scala @@ -6,7 +6,6 @@ import libs.xml.XMLRead import libs.xml.XmlUtil.XmlCleaner import libs.xml.implicits._ import libs.xml.syntax._ -import java.time.{LocalDateTime, Clock} import play.api.libs.functional.syntax.{unlift, _} import play.api.libs.json.Reads._ import play.api.libs.json.Writes._ @@ -62,9 +61,9 @@ case class UserExtractTask( } object UserExtractTask extends ReadableEntity[UserExtractTask] { - implicit val dateFormats = DateUtils.utcDateTimeFormats + implicit val dateFormats: Format[LocalDateTime] = DateUtils.utcDateTimeFormats - def instance(tenant: String, orgKey: String, userId: String, email: String) = + def instance(tenant: String, orgKey: String, userId: String, email: String): UserExtractTask = UserExtractTask( _id = BSONObjectID.generate().stringify, tenant = tenant, @@ -113,8 +112,8 @@ object UserExtractTask extends ReadableEntity[UserExtractTask] { (JsPath \ "endedAt").writeNullable[LocalDateTime] )(unlift(UserExtractTask.unapply)) - implicit val format = Format(userExtractTaskReads, userExtractTaskWrites) - implicit val oformat = OFormat(userExtractTaskReads, userExtractTaskOWrites) + implicit val format: Format[UserExtractTask] = Format(userExtractTaskReads, userExtractTaskWrites) + implicit val oformat: OFormat[UserExtractTask] = OFormat(userExtractTaskReads, userExtractTaskOWrites) implicit val userExtractTaskReadXml: XMLRead[UserExtractTask] = (node: NodeSeq, path: Option[String]) => diff --git a/nio-server/app/models/common.scala b/nio-server/app/models/common.scala index 7a8b2205..ee04a463 100644 --- a/nio-server/app/models/common.scala +++ b/nio-server/app/models/common.scala @@ -11,15 +11,15 @@ import libs.xml.XmlUtil.XmlCleaner import scala.collection.Seq case class AppDone(orgKey: String, userId: String, appId: String) { - def asJson() = AppDone.appDoneFormats.writes(this) + def asJson(): JsObject = AppDone.appDoneFormats.writes(this) } object AppDone { - implicit val appDoneFormats = Json.format[AppDone] + implicit val appDoneFormats: OFormat[AppDone] = Json.format[AppDone] } case class AppIds(appIds: Seq[String]) { - def asXml() = + def asXml(): Elem = { appIds.map(appId => {appId} @@ -29,9 +29,9 @@ case class AppIds(appIds: Seq[String]) { } object AppIds extends ReadableEntity[AppIds] { - implicit val appIdsFormats = Json.format[AppIds] + implicit val appIdsFormats: OFormat[AppIds] = Json.format[AppIds] - def fromXml(xml: Elem) = + def fromXml(xml: Elem): Either[AppErrors, AppIds] = Try { val appIds = (xml \\ "appId").map(_.head.text) AppIds(appIds) @@ -41,7 +41,7 @@ object AppIds extends ReadableEntity[AppIds] { Left(AppErrors.fromXmlError(throwable)) } - def fromJson(json: JsValue) = + def fromJson(json: JsValue): Either[AppErrors, AppIds] = json.validate[AppIds](appIdsFormats) match { case JsSuccess(o, _) => Right(o) case JsError(errors) => Left(AppErrors.fromJsError(errors)) @@ -60,9 +60,9 @@ object Digest { } case class AppFilesMetadata(orgKey: String, userId: String, appId: String, files: Seq[FileMetadata]) { - def asJson() = AppFilesMetadata.appFilesMetadataFormats.writes(this) + def asJson(): JsObject = AppFilesMetadata.appFilesMetadataFormats.writes(this) } object AppFilesMetadata { - implicit val appFilesMetadataFormats = Json.format[AppFilesMetadata] + implicit val appFilesMetadataFormats: OFormat[AppFilesMetadata] = Json.format[AppFilesMetadata] } diff --git a/nio-server/app/models/events.scala b/nio-server/app/models/events.scala index 68398f7c..d9e1ecca 100644 --- a/nio-server/app/models/events.scala +++ b/nio-server/app/models/events.scala @@ -1,8 +1,5 @@ package models -import models.OrganisationUser.{read, write} -import java.time.{LocalDateTime, Clock} - import java.time.{Clock, LocalDateTime} import utils.NioLogger import play.api.libs.json._ @@ -147,10 +144,8 @@ object CleanUpMetadata { "metadata".equals(v._1) && (JsNull.equals(v._2) || JsObject.empty .equals(v._2)) ) match { - case v if v => - jsObject.-("metadata") - case v if !v => - jsObject + case v if v => jsObject.-("metadata") + case _ => jsObject } } @@ -166,11 +161,11 @@ case class TenantCreated( id: Long = NioEvent.gen.nextId(), payload: Tenant ) extends NioEvent { - def shardId = payload.key + def shardId: String = payload.key - def `type` = EventType.TenantCreated + def `type`: EventType.Value = EventType.TenantCreated - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -192,11 +187,11 @@ case class TenantDeleted( id: Long = NioEvent.gen.nextId(), payload: Tenant ) extends NioEvent { - def shardId = payload.key + def shardId: String = payload.key - def `type` = EventType.TenantDeleted + def `type`: EventType.Value = EventType.TenantDeleted - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -218,11 +213,11 @@ case class OrganisationCreated( id: Long = NioEvent.gen.nextId(), payload: Organisation ) extends NioEvent { - def shardId = payload.key + def shardId: String = payload.key - def `type` = EventType.OrganisationCreated + def `type`: EventType.Value = EventType.OrganisationCreated - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -245,11 +240,11 @@ case class OrganisationUpdated( payload: Organisation, oldValue: Organisation ) extends NioEvent { - def shardId = oldValue.key + def shardId: String = oldValue.key - def `type` = EventType.OrganisationUpdated + def `type`: EventType.Value = EventType.OrganisationUpdated - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -272,11 +267,11 @@ case class OrganisationReleased( id: Long = NioEvent.gen.nextId(), payload: Organisation ) extends NioEvent { - def shardId = payload.key + def shardId: String = payload.key - def `type` = EventType.OrganisationReleased + def `type`: EventType.Value = EventType.OrganisationReleased - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -298,11 +293,11 @@ case class OrganisationDeleted( id: Long = NioEvent.gen.nextId(), payload: Organisation ) extends NioEvent { - def shardId = payload.key + def shardId: String = payload.key - def `type` = EventType.OrganisationDeleted + def `type`: EventType.Value = EventType.OrganisationDeleted - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -325,11 +320,11 @@ case class ConsentFactCreated( payload: ConsentFact, command: JsValue ) extends NioEvent() { - def shardId = payload.userId + def shardId: String = payload.userId - def `type` = EventType.ConsentFactCreated + def `type`: EventType.Value = EventType.ConsentFactCreated - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -354,11 +349,11 @@ case class ConsentFactUpdated( oldValue: ConsentFact, command: JsValue ) extends NioEvent { - def shardId = oldValue.userId + def shardId: String = oldValue.userId - def `type` = EventType.ConsentFactUpdated + def `type`: EventType.Value = EventType.ConsentFactUpdated - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -382,11 +377,11 @@ case class AccountDeleted( date: LocalDateTime = LocalDateTime.now(Clock.systemUTC), payload: Account ) extends NioEvent { - def shardId = payload.accountId + def shardId: String = payload.accountId - def `type` = EventType.AccountDeleted + def `type`: EventType.Value = EventType.AccountDeleted - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -409,11 +404,11 @@ case class AccountCreated( date: LocalDateTime = LocalDateTime.now(Clock.systemUTC), payload: Account ) extends NioEvent { - def shardId = payload.accountId + def shardId: String = payload.accountId - def `type` = EventType.AccountCreated + def `type`: EventType.Value = EventType.AccountCreated - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -437,11 +432,11 @@ case class AccountUpdated( payload: Account, oldValue: Account ) extends NioEvent { - def shardId = payload.accountId + def shardId: String = payload.accountId - def `type` = EventType.AccountUpdated + def `type`: EventType.Value = EventType.AccountUpdated - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -462,9 +457,9 @@ case class SecuredEvent( date: LocalDateTime = LocalDateTime.now(Clock.systemUTC), payload: Digest ) extends NioEvent { - def `type` = EventType.SecuredEvent + def `type`: EventType.Value = EventType.SecuredEvent - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -488,11 +483,11 @@ case class DeletionStarted( date: LocalDateTime = LocalDateTime.now(Clock.systemUTC), payload: DeletionTaskInfoPerApp ) extends NioEvent { - def shardId = payload.userId + def shardId: String = payload.userId - def `type` = EventType.DeletionStarted + def `type`: EventType.Value = EventType.DeletionStarted - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -514,11 +509,11 @@ case class DeletionAppDone( date: LocalDateTime = LocalDateTime.now(Clock.systemUTC), payload: AppDone ) extends NioEvent { - def shardId = payload.userId + def shardId: String = payload.userId - def `type` = EventType.DeletionAppDone + def `type`: EventType.Value = EventType.DeletionAppDone - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -540,11 +535,11 @@ case class DeletionFinished( date: LocalDateTime = LocalDateTime.now(Clock.systemUTC), payload: DeletionTask ) extends NioEvent { - def shardId = payload.userId + def shardId: String = payload.userId - def `type` = EventType.DeletionFinished + def `type`: EventType.Value = EventType.DeletionFinished - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -617,11 +612,11 @@ case class ExtractionStarted( date: LocalDateTime = LocalDateTime.now(Clock.systemUTC), payload: ExtractionTaskInfoPerApp ) extends NioEvent { - def shardId = payload.userId + def shardId: String = payload.userId - def `type` = EventType.ExtractionStarted + def `type`: EventType.Value = EventType.ExtractionStarted - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -643,11 +638,11 @@ case class ExtractionAppDone( date: LocalDateTime = LocalDateTime.now(Clock.systemUTC), payload: AppDone ) extends NioEvent { - def shardId = payload.userId + def shardId: String = payload.userId - def `type` = EventType.ExtractionAppDone + def `type`: EventType.Value = EventType.ExtractionAppDone - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -669,11 +664,11 @@ case class ExtractionAppFilesMetadataReceived( date: LocalDateTime = LocalDateTime.now(Clock.systemUTC), payload: AppFilesMetadata ) extends NioEvent { - def shardId = payload.userId + def shardId: String = payload.userId - def `type` = EventType.ExtractionAppFilesMetadataReceived + def `type`: EventType.Value = EventType.ExtractionAppFilesMetadataReceived - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, @@ -695,11 +690,11 @@ case class ExtractionFinished( date: LocalDateTime = LocalDateTime.now(Clock.systemUTC), payload: ExtractionTask ) extends NioEvent { - def shardId = payload.userId + def shardId: String = payload.userId - def `type` = EventType.ExtractionFinished + def `type`: EventType.Value = EventType.ExtractionFinished - def asJson() = + def asJson(): JsObject = Json .obj( "type" -> `type`, diff --git a/nio-server/app/service/AccessibleOfferManagerService.scala b/nio-server/app/service/AccessibleOfferManagerService.scala index 0bcc1a75..4fc79afa 100644 --- a/nio-server/app/service/AccessibleOfferManagerService.scala +++ b/nio-server/app/service/AccessibleOfferManagerService.scala @@ -1,6 +1,5 @@ package service -import java.util.regex.Pattern import org.apache.pekko.http.scaladsl.util.FastFuture import controllers.AppErrorWithStatus diff --git a/nio-server/app/service/CatchupNioEventService.scala b/nio-server/app/service/CatchupNioEventService.scala index 910cf549..eb406552 100644 --- a/nio-server/app/service/CatchupNioEventService.scala +++ b/nio-server/app/service/CatchupNioEventService.scala @@ -32,9 +32,9 @@ class CatchupNioEventService( implicit val materializer: Materializer = Materializer(system) - def getUnsendConsentFactAsSource(tenant: String): Source[Catchup, Future[State]] = + private def getUnsendConsentFactAsSource(tenant: String): Source[Catchup, Future[State]] = Source - .fromFutureSource( + .futureSource( consentFactMongoDataStore .streamByQuery(tenant, Json.obj("sendToKafka" -> false)) ) @@ -48,14 +48,14 @@ class CatchupNioEventService( } .mapConcat(_.toList) - def getUnrelevantConsentFactsFlow: Flow[Catchup, Catchup, NotUsed] = + private def getUnrelevantConsentFactsFlow: Flow[Catchup, Catchup, NotUsed] = Flow[Catchup] .filterNot { catchup => catchup.lastConsentFact.lastUpdateSystem .isEqual(catchup.consentFact.lastUpdateSystem) } - def getRelevantConsentFactsAsFlow(tenant: String): Flow[Catchup, Catchup, NotUsed] = + private def getRelevantConsentFactsAsFlow(tenant: String): Flow[Catchup, Catchup, NotUsed] = Flow[Catchup] .filter { catchup => env.config.kafka.catchUpEvents.strategy == "All" || @@ -63,7 +63,7 @@ class CatchupNioEventService( .isEqual(catchup.consentFact.lastUpdateSystem) } - def resendNioEventsFlow(tenant: String): Flow[Catchup, Catchup, NotUsed] = + private def resendNioEventsFlow(tenant: String): Flow[Catchup, Catchup, NotUsed] = Flow[Catchup] .map { catchup => val event = if (catchup.isCreation) { @@ -94,7 +94,7 @@ class CatchupNioEventService( .map(_.passThrough) .via(unflagConsentFactsFlow(tenant)) - def unflagConsentFactsFlow(tenant: String): Flow[Catchup, Catchup, NotUsed] = + private def unflagConsentFactsFlow(tenant: String): Flow[Catchup, Catchup, NotUsed] = Flow[Catchup] .grouped(200) .mapAsync(1) { catchups => @@ -109,16 +109,12 @@ class CatchupNioEventService( } .mapConcat(_.toList) - def catchupNioEventScheduler() = + def catchupNioEventScheduler(): Any = if (env.env == "prod") { tenantMongoDataStore.findAll().map { tenants => tenants.map { tenant => NioLogger.debug(s"start catchup scheduler for tenant ${tenant.key}") - system.scheduler.schedule( - env.config.kafka.catchUpEvents.delay, - env.config.kafka.catchUpEvents.interval, - new Runnable() { - def run = + system.scheduler.scheduleWithFixedDelay(env.config.kafka.catchUpEvents.delay, env.config.kafka.catchUpEvents.interval) ({ () => catchupLockMongoDatastore .findLock(tenant.key) .map { @@ -167,8 +163,7 @@ class CatchupNioEventService( case false => () } } - } - ) + }) } } } diff --git a/nio-server/app/service/ConsentManagerService.scala b/nio-server/app/service/ConsentManagerService.scala index 5e59d9a5..a117f2bd 100644 --- a/nio-server/app/service/ConsentManagerService.scala +++ b/nio-server/app/service/ConsentManagerService.scala @@ -41,7 +41,7 @@ class ConsentManagerService( for { lastConsent <- IO.fromFutureOption(lastConsentFactMongoDataStore.findByOrgKeyAndUserId(tenant, organisationKey, userId), AppErrorWithStatus(s"consentfact.${userId}.not.found", NotFound)) organisation <- IO.fromFutureOption(organisationMongoDataStore.findLastReleasedByKey(tenant, organisationKey), {NioLogger.error(s"error.specified.org.never.released for organisation key $organisationKey"); AppErrorWithStatus("error.specified.org.never.released")}) - consentFact = partialConsentFact.applyTo(lastConsent, organisation.version) + consentFact = partialConsentFact.applyTo(lastConsent, organisation) result <- createOrReplace(tenant, author, metadata, organisation, consentFact, Some(lastConsent), command = command) } yield result } @@ -51,16 +51,17 @@ class ConsentManagerService( author: String, metadata: Option[Seq[(String, String)]], organisation: Organisation, - consentFact: ConsentFact, + consentFactInput: ConsentFact, maybeLastConsentFact: Option[ConsentFact] = None, command: JsValue ): IO[AppErrorWithStatus, ConsentFact] = for { - _ <- IO.fromEither(organisation.isValidWith(consentFact, maybeLastConsentFact)).mapError(m => AppErrorWithStatus(m)) - organisationKey: String = organisation.key - userId: String = consentFact.userId - _ <- validateOffersStructures(tenant, organisationKey, userId, consentFact.offers).doOnError{ e => NioLogger.error(s"validate offers structure $e") } - res <- maybeLastConsentFact match { + _ <- IO.fromEither(organisation.isValidWith(consentFactInput, maybeLastConsentFact)).mapError(m => AppErrorWithStatus(m)) + consentFact: ConsentFact = consentFactInput.setUpValidityPeriods(organisation) + organisationKey: String = organisation.key + userId: String = consentFact.userId + _ <- validateOffersStructures(tenant, organisationKey, userId, consentFact.offers).doOnError{ e => NioLogger.error(s"validate offers structure $e") } + res <- maybeLastConsentFact match { // Create a new user, consent fact and last consent fact case None => for { @@ -336,7 +337,7 @@ class ConsentManagerService( } // Cannot rollback and update a consent fact to an old organisation version - case Some(lastConsentFactStored) if lastConsentFactStored.version >= consentFact.version => + case Some(lastConsentFactStored) => NioLogger.error( s"error.version.lower.than.stored : last version saved ${lastConsentFactStored.version} -> version specified ${consentFact.version}" ) diff --git a/nio-server/app/service/MailService.scala b/nio-server/app/service/MailService.scala index cdae93a6..a6910916 100644 --- a/nio-server/app/service/MailService.scala +++ b/nio-server/app/service/MailService.scala @@ -5,9 +5,7 @@ import org.apache.pekko.http.scaladsl.util.FastFuture import configuration.{Env, MailGunConfig} import utils.NioLogger import play.api.libs.ws.{WSClient, _} -import play.api.mvc.MultipartFormData.DataPart -import scala.collection.immutable import scala.concurrent.{ExecutionContext, Future} trait MailService { diff --git a/nio-server/app/service/OfferManagerService.scala b/nio-server/app/service/OfferManagerService.scala index fb1c3c8d..4966e615 100644 --- a/nio-server/app/service/OfferManagerService.scala +++ b/nio-server/app/service/OfferManagerService.scala @@ -39,12 +39,9 @@ class OfferManagerService( offerRestrictionPatterns: Option[Seq[String]] ): Future[Either[AppErrorWithStatus, Offer]] = maybeOfferKey match { - case Some(offerKey) if offerKey != offer.key => - toErrorWithStatus(s"offer.key.${offer.key}.must.be.equals.to.$offerKey", BadRequest) - case Some(offerKey) if offerKey == offer.key => - save(tenant, orgKey, offer, offerRestrictionPatterns) - case None => - save(tenant, orgKey, offer, offerRestrictionPatterns) + case Some(offerKey) if offerKey != offer.key => toErrorWithStatus(s"offer.key.${offer.key}.must.be.equals.to.$offerKey", BadRequest) + case Some(_) => save(tenant, orgKey, offer, offerRestrictionPatterns) + case None => save(tenant, orgKey, offer, offerRestrictionPatterns) } private def save( @@ -53,39 +50,38 @@ class OfferManagerService( offer: Offer, offerRestrictionPatterns: Option[Seq[String]] ): Future[Either[AppErrorWithStatus, Offer]] = - accessibleOfferManagerService.accessibleOfferKey(offer.key, offerRestrictionPatterns) match { - case true => - organisationMongoDataStore - .findOffer(tenant, orgKey, offer.key) - .flatMap { - case Left(e) => - toErrorWithStatus(e, NotFound) - case Right(maybeOffer) => - maybeOffer match { - case Some(_) => - updateOrganisation(tenant, orgKey).flatMap { - case Right(_) => - organisationMongoDataStore - .updateOffer(tenant, orgKey, offer.key, offer) - .map(Right(_)) + if (accessibleOfferManagerService.accessibleOfferKey(offer.key, offerRestrictionPatterns)) { + organisationMongoDataStore + .findOffer(tenant, orgKey, offer.key) + .flatMap { + case Left(e) => + toErrorWithStatus(e, NotFound) + case Right(maybeOffer) => + maybeOffer match { + case Some(_) => + updateOrganisation(tenant, orgKey).flatMap { + case Right(_) => + organisationMongoDataStore + .updateOffer(tenant, orgKey, offer.key, offer) + .map(Right(_)) - case Left(e) => - toErrorWithStatus(e) - } - case None => - updateOrganisation(tenant, orgKey).flatMap { - case Right(_) => - organisationMongoDataStore - .addOffer(tenant, orgKey, offer) - .map(Right(_)) + case Left(e) => + toErrorWithStatus(e) + } + case None => + updateOrganisation(tenant, orgKey).flatMap { + case Right(_) => + organisationMongoDataStore + .addOffer(tenant, orgKey, offer) + .map(Right(_)) - case Left(e) => - toErrorWithStatus(e) - } - } - } - case false => - toErrorWithStatus(s"offer.${offer.key}.not.accessible", Unauthorized) + case Left(e) => + toErrorWithStatus(e) + } + } + } + } else { + toErrorWithStatus(s"offer.${offer.key}.not.accessible", Unauthorized) } def delete( diff --git a/nio-server/app/utils/BSONUtils.scala b/nio-server/app/utils/BSONUtils.scala index 6805887e..22f51c76 100644 --- a/nio-server/app/utils/BSONUtils.scala +++ b/nio-server/app/utils/BSONUtils.scala @@ -1,26 +1,8 @@ package utils import play.api.libs.json.Json -import reactivemongo.api.bson.{ - BSONArray, - BSONBoolean, - BSONDateTime, - BSONDecimal, - BSONDocument, - BSONDouble, - BSONElement, - BSONInteger, - BSONLong, - BSONMaxKey, - BSONMinKey, - BSONNull, - BSONObjectID, - BSONString, - BSONTimestamp, - BSONUndefined -} +import reactivemongo.api.bson.BSONDocument -import scala.util.{Failure, Success, Try} object BSONUtils { def stringify(doc: BSONDocument): String = { diff --git a/nio-server/app/utils/DateUtils.scala b/nio-server/app/utils/DateUtils.scala index dace43f8..a8169e65 100644 --- a/nio-server/app/utils/DateUtils.scala +++ b/nio-server/app/utils/DateUtils.scala @@ -14,7 +14,7 @@ object DateUtils { case JsString(s) => Try(LocalDateTime.parse(s, utcDateFormatter)) match { case Success(d) => JsSuccess(d.withNano(0)) - case Failure(f) => JsSuccess(null) + case Failure(_) => JsSuccess(null) } case _ => JsError("error.expected.date") } diff --git a/nio-server/app/utils/DefaultLoader.scala b/nio-server/app/utils/DefaultLoader.scala index cccdbaff..f8c0fbe2 100644 --- a/nio-server/app/utils/DefaultLoader.scala +++ b/nio-server/app/utils/DefaultLoader.scala @@ -4,6 +4,7 @@ import db.OrganisationMongoDataStore import models._ import play.api.Configuration +import scala.concurrent.duration.FiniteDuration import scala.concurrent.{ExecutionContext, Future} class DefaultLoader(conf: Configuration, organisationStore: OrganisationMongoDataStore)(implicit ec: ExecutionContext) { @@ -27,7 +28,9 @@ class DefaultLoader(conf: Configuration, organisationStore: OrganisationMongoDat permissions = groupsConf.get[Seq[Configuration]]("permissions").map { permissionsConf => Permission( key = permissionsConf.get[String]("key"), - label = permissionsConf.get[String]("label") + label = permissionsConf.get[String]("label"), + `type` = permissionsConf.getOptional[String]("type").flatMap(s => PermissionType.parse(s).toOption).getOrElse(OptIn), + validityPeriod = permissionsConf.getOptional[FiniteDuration]("validityPeriod") ) } ) diff --git a/nio-server/app/utils/IdGenerator.scala b/nio-server/app/utils/IdGenerator.scala index ae6ca93f..1d03646d 100644 --- a/nio-server/app/utils/IdGenerator.scala +++ b/nio-server/app/utils/IdGenerator.scala @@ -44,8 +44,8 @@ object IdGenerator { c match { case i if i == 9 || i == 14 || i == 19 || i == 24 => "-" case i if i == 15 => "4" - case i if c == 20 => INIT_STRING((Random.nextDouble() * 4.0).toInt | 8) - case i => INIT_STRING((Random.nextDouble() * 15.0).toInt | 0) + case _ if c == 20 => INIT_STRING((Random.nextDouble() * 4.0).toInt | 8) + case _ => INIT_STRING((Random.nextDouble() * 15.0).toInt | 0) }).mkString("") def token(characters: Array[String], size: Int): String = diff --git a/nio-server/app/utils/Results.scala b/nio-server/app/utils/Results.scala index e36731eb..e383a33f 100644 --- a/nio-server/app/utils/Results.scala +++ b/nio-server/app/utils/Results.scala @@ -5,12 +5,14 @@ import cats.kernel.Monoid import play.api.libs.json._ import utils.Result.{AppErrors, ErrorMessage, Result} import libs.xml.XmlUtil.XmlCleaner + import scala.collection.Seq +import scala.xml.Elem object Result { case class ErrorMessage(message: String, args: String*) { - def asXml() = + def asXml(): Elem = {message} @@ -22,7 +24,7 @@ object Result { } object ErrorMessage { - implicit val format = Json.format[ErrorMessage] + implicit val format: OFormat[ErrorMessage] = Json.format[ErrorMessage] } case class AppErrors( @@ -30,19 +32,19 @@ object Result { fieldErrors: Map[String, List[ErrorMessage]] = Map.empty ) { - def ++(s: AppErrors) = + def ++(s: AppErrors): AppErrors = this.copy(errors = errors ++ s.errors, fieldErrors = fieldErrors ++ s.fieldErrors) - def addFieldError(field: String, errors: List[ErrorMessage]) = + def addFieldError(field: String, errors: List[ErrorMessage]): AppErrors = fieldErrors.get(field) match { case Some(err) => AppErrors(errors, fieldErrors + (field -> (err ++ errors))) case None => AppErrors(errors, fieldErrors + (field -> errors)) } - def asJson() = Json.toJson(this)(AppErrors.format) + def asJson(): JsValue = Json.toJson(this)(AppErrors.format) - def asXml() = + def asXml(): Elem = {errors.map(_.asXml())} @@ -69,7 +71,7 @@ object Result { import cats.instances.all._ import cats.syntax.semigroup._ - implicit val format = Json.format[AppErrors] + implicit val format: OFormat[AppErrors] = Json.format[AppErrors] def fromJsError(jsError: Seq[(JsPath, Seq[JsonValidationError])]): AppErrors = { val fieldErrors = jsError.map { case (k, v) => @@ -93,9 +95,9 @@ object Result { } implicit val monoid: Monoid[AppErrors] = new Monoid[AppErrors] { - override def empty = AppErrors() + override def empty: AppErrors = AppErrors() - override def combine(x: AppErrors, y: AppErrors) = { + override def combine(x: AppErrors, y: AppErrors): AppErrors = { val errors = x.errors ++ y.errors val fieldErrors = mergeMap(x.fieldErrors, y.fieldErrors) AppErrors(errors, fieldErrors) @@ -126,7 +128,7 @@ object Result { } case class ImportResult(success: Int = 0, errors: AppErrors = AppErrors()) { - def isError = !errors.isEmpty + def isError: Boolean = !errors.isEmpty def toJson: JsValue = ImportResult.format.writes(this) } @@ -135,18 +137,18 @@ object ImportResult { import cats.syntax.semigroup._ - implicit val format = Json.format[ImportResult] + implicit val format: OFormat[ImportResult] = Json.format[ImportResult] - implicit val monoid = new Monoid[ImportResult] { - override def empty = ImportResult() + implicit val monoid: Monoid[ImportResult] = new Monoid[ImportResult] { + override def empty: ImportResult = ImportResult() - override def combine(x: ImportResult, y: ImportResult) = (x, y) match { + override def combine(x: ImportResult, y: ImportResult): ImportResult = (x, y) match { case (ImportResult(s1, e1), ImportResult(s2, e2)) => ImportResult(s1 + s2, e1 |+| e2) } } - def error(e: ErrorMessage) = ImportResult(errors = AppErrors(errors = Seq(e))) + def error(e: ErrorMessage): ImportResult = ImportResult(errors = AppErrors(errors = Seq(e))) def fromResult[T](r: Result[T]): ImportResult = r match { case Right(_) => ImportResult(success = 1) diff --git a/nio-server/app/utils/S3Manager.scala b/nio-server/app/utils/S3Manager.scala index 8247b321..45c0d0ab 100644 --- a/nio-server/app/utils/S3Manager.scala +++ b/nio-server/app/utils/S3Manager.scala @@ -1,8 +1,6 @@ package utils -import java.io.{ByteArrayInputStream, File, FileInputStream, InputStream} -import java.nio.file.Files -import java.util +import java.io.{ByteArrayInputStream, InputStream} import java.util.Base64 import org.apache.pekko.NotUsed @@ -13,13 +11,10 @@ import org.apache.pekko.util.ByteString import com.amazonaws.ClientConfiguration import com.amazonaws.auth.{AWSStaticCredentialsProvider, BasicAWSCredentials} import com.amazonaws.client.builder.AwsClientBuilder -import com.amazonaws.regions.Regions import com.amazonaws.services.s3.model._ import com.amazonaws.services.s3.model.lifecycle.{ - LifecycleAndOperator, LifecycleFilter, - LifecyclePrefixPredicate, - LifecycleTagPredicate + LifecyclePrefixPredicate } import com.amazonaws.services.s3.{AmazonS3, AmazonS3ClientBuilder} import configuration.{Env, S3Config} @@ -48,7 +43,7 @@ trait FSUserExtractManager { class S3Manager(env: Env, actorSystem: ActorSystem) extends FSManager with FSUserExtractManager { - implicit val mat = Materializer(actorSystem) + implicit val mat: Materializer = Materializer(actorSystem) private lazy val s3Config: S3Config = env.config.s3Config private lazy val amazonS3: AmazonS3 = AmazonS3ClientBuilder @@ -116,8 +111,8 @@ class S3Manager(env: Env, actorSystem: ActorSystem) extends FSManager with FSUse } } - def bucketExpiration(bucketName: String, prefixKey: String)(implicit - s3ExecutionContext: S3ExecutionContext + private def bucketExpiration(bucketName: String, prefixKey: String)(implicit + s3ExecutionContext: S3ExecutionContext ): Future[Boolean] = { // https://docs.aws.amazon.com/fr_fr/AmazonS3/latest/dev/manage-lifecycle-using-java.html val rule: BucketLifecycleConfiguration.Rule = @@ -184,8 +179,8 @@ class S3Manager(env: Env, actorSystem: ActorSystem) extends FSManager with FSUse } } - def loadFile(key: String, uploadId: String)(implicit - s3ExecutionContext: S3ExecutionContext + private def loadFile(key: String, uploadId: String)(implicit + s3ExecutionContext: S3ExecutionContext ): Flow[ByteString, UploadPartResult, NotUsed] = Flow[ByteString] .grouped(s3Config.chunkSizeInMb) diff --git a/nio-server/app/utils/SecureEvent.scala b/nio-server/app/utils/SecureEvent.scala index a643254c..d609c5ff 100644 --- a/nio-server/app/utils/SecureEvent.scala +++ b/nio-server/app/utils/SecureEvent.scala @@ -1,12 +1,13 @@ package utils import org.apache.pekko.actor.ActorSystem -import org.apache.pekko.stream.Materializer +import org.apache.pekko.stream.{Materializer, RestartSettings} import org.apache.pekko.stream.scaladsl.{RestartSource, Sink} import configuration.Env import messaging.KafkaMessageBroker +import org.apache.pekko.Done -import scala.concurrent.ExecutionContext +import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.duration.DurationDouble trait TSecureEvent {} @@ -18,21 +19,21 @@ class SecureEvent( implicit val executionContext: ExecutionContext ) extends TSecureEvent { - implicit val materializer = Materializer(actorSystem) - lazy val kafkaConfig = env.config.kafka + implicit val materializer: Materializer = Materializer(actorSystem) + private lazy val kafkaConfig = env.config.kafka - private def run() = { + private def run(): Future[Done] = { NioLogger.info("secure event : run") val source = kafkaMessageBroker.readAllEvents(kafkaConfig.eventsGroupIn, kafkaConfig.eventsGroupDuration) // Auto restart RestartSource - .withBackoff( + .withBackoff(RestartSettings( 3.seconds, // min backoff 30.seconds, // max backoff 0.2 // adds 20% "noise" to vary the intervals slightly - ) { () => + )) { () => NioLogger.debug("secure event : restart source") source } @@ -40,7 +41,7 @@ class SecureEvent( } - def initialize() = + def initialize(): Any = if (env.config.recordManagementEnabled && env.config.s3ManagementEnabled) { run() } diff --git a/nio-server/build.sbt b/nio-server/build.sbt index 08657bea..9f53de79 100644 --- a/nio-server/build.sbt +++ b/nio-server/build.sbt @@ -9,7 +9,7 @@ lazy val `nio-server` = (project in file(".")) .enablePlugins(NoPublish) .disablePlugins(BintrayPlugin) -scalaVersion := "2.13.14" +scalaVersion := Dependencies._scalaVersion resolvers ++= Seq( Resolver.jcenterRepo, @@ -21,6 +21,8 @@ dependencyOverrides ++= Seq( "org.scala-lang.modules" %% "scala-xml" % "2.2.0" ) +ThisBuild / scalafixDependencies ++= Seq("org.reactivemongo" %% "reactivemongo-scalafix" % "1.1.0-RC13") + libraryDependencies ++= Seq( ws, "org.apache.pekko" %% "pekko-stream" % pekko, @@ -57,7 +59,8 @@ scalacOptions ++= Seq( "-feature", "-language:higherKinds", "-language:implicitConversions", - "-language:existentials" + "-language:existentials", + "-Wunused:imports" //, "-Xsource:3" ) /// ASSEMBLY CONFIG @@ -80,14 +83,14 @@ assembly / assemblyMergeStrategy := { case PathList(ps @ _*) if ps.contains("buildinfo") => MergeStrategy.discard case PathList(ps @ _*) if ps.last endsWith "reflection-config.json" => MergeStrategy.first case o => - val oldStrategy = (assemblyMergeStrategy in assembly).value + val oldStrategy = (assembly / assemblyMergeStrategy).value oldStrategy(o) } lazy val packageAll = taskKey[Unit]("PackageAll") packageAll := { - (dist in Compile).value - (assembly in Compile).value + (Compile / dist).value + (Compile / assembly).value } /// DOCKER CONFIG @@ -95,9 +98,9 @@ packageAll := { dockerExposedPorts := Seq( 9000 ) -packageName in Docker := "nio" +Docker / packageName := "nio" -maintainer in Docker := "MAIF Team " +Docker / maintainer := "MAIF Team " dockerBaseImage := "openjdk:8" @@ -134,4 +137,4 @@ dockerEntrypoint ++= Seq( dockerUpdateLatest := true -packageName in Universal := s"nio" +Universal / packageName := s"nio" diff --git a/nio-server/conf/application.conf b/nio-server/conf/application.conf index a5f3ccf9..dd363119 100644 --- a/nio-server/conf/application.conf +++ b/nio-server/conf/application.conf @@ -237,5 +237,3 @@ healthcheck { mongodb.uri = "mongodb://localhost:27017/nio" mongodb.uri = ${?MONGODB_ADDON_URI} - -include "default" \ No newline at end of file diff --git a/nio-server/conf/default.conf b/nio-server/conf/dataset.conf similarity index 96% rename from nio-server/conf/default.conf rename to nio-server/conf/dataset.conf index 05e7d5dc..3670fb7b 100644 --- a/nio-server/conf/default.conf +++ b/nio-server/conf/dataset.conf @@ -12,6 +12,7 @@ organisations = [ { key = "phone" label = "Par contact téléphonique" + validityPeriod = "1 hour" }, { key = "mail" @@ -56,6 +57,7 @@ organisations = [ { key = "phone" label = "Par contact téléphonique" + validityPeriod = "1 hour" }, { key = "mail" @@ -100,6 +102,7 @@ organisations = [ { key = "phone" label = "Par contact téléphonique" + validityPeriod = "1 hour" }, { key = "mail" diff --git a/nio-server/conf/dev.conf b/nio-server/conf/dev.conf new file mode 100644 index 00000000..93b4e95e --- /dev/null +++ b/nio-server/conf/dev.conf @@ -0,0 +1,4 @@ +include "application" +include "dataset" + +db.flush=true \ No newline at end of file diff --git a/nio-server/conf/routes b/nio-server/conf/routes index d26d4b10..b8955067 100644 --- a/nio-server/conf/routes +++ b/nio-server/conf/routes @@ -109,7 +109,7 @@ PATCH /api/:tenant/organisations/:orgKey/users/:userId DELETE /api/:tenant/organisations/:orgKey/users/:userId/offers/:offerKey controllers.ConsentController.deleteOffer(tenant: String, orgKey: String, userId: String, offerKey: String) -GET /api/:tenant/organisations/:orgKey/users/:userId controllers.ConsentController.find(tenant: String, orgKey: String, userId: String) +GET /api/:tenant/organisations/:orgKey/users/:userId controllers.ConsentController.find(tenant: String, orgKey: String, userId: String, showExpiredConsents: Boolean ?= false) GET /api/:tenant/organisations/:orgKey/users/:userId/logs controllers.ConsentController.getConsentFactHistory(tenant: String, orgKey: String, userId: String, page: Int ?= 0, pageSize: Int ?= 10) @@ -139,7 +139,7 @@ POST /api/tenants DELETE /api/tenants/:tenantKey controllers.TenantController.deleteTenant(tenantKey) -GET /_healthCheck controllers.MetricsController.healthCheck +GET /_healthCheck controllers.MetricsController.healthCheck() GET /_download/*name controllers.UserExtractController.downloadFile(name: String, uploadToken: String) diff --git a/nio-server/javascript/.nvmrc b/nio-server/javascript/.nvmrc new file mode 100644 index 00000000..4f426dd6 --- /dev/null +++ b/nio-server/javascript/.nvmrc @@ -0,0 +1 @@ +v11.8.0 \ No newline at end of file diff --git a/nio-server/javascript/src/nio/pages/Consents.js b/nio-server/javascript/src/nio/pages/Consents.js index c5800586..fc3691b9 100644 --- a/nio-server/javascript/src/nio/pages/Consents.js +++ b/nio-server/javascript/src/nio/pages/Consents.js @@ -182,7 +182,10 @@ class Consents extends Component { checked={consent.checked} onChange={() => this.onChangeConsents(consent.key, !consent.checked)}/> + htmlFor={`${this.props.index}-${index}-${consent.key}`}>{consent.label} + {consent.expiredAt && (new Date(consent.expiredAt) >= new Date()) && (expire le {new Date(consent.expiredAt).toLocaleString()})} + {consent.expiredAt && (new Date(consent.expiredAt) < new Date()) && (a expiré le {new Date(consent.expiredAt).toLocaleString()})} + ) diff --git a/nio-server/javascript/src/nio/pages/GroupPermissionPage.js b/nio-server/javascript/src/nio/pages/GroupPermissionPage.js index b84e5d91..e5d37c79 100644 --- a/nio-server/javascript/src/nio/pages/GroupPermissionPage.js +++ b/nio-server/javascript/src/nio/pages/GroupPermissionPage.js @@ -200,6 +200,13 @@ class Permission extends Component { possibleValues={["OptIn", "OptOut"]} errorKey={`${this.props.prefixe}permissions.${this.props.index}.type.required`}/> + this.onChange(e, "validityPeriod")} + disabled={this.props.readOnlyMode} + errorMessage={this.props.errors} + errorKey={`${this.props.prefixe}permissions.${this.props.index}.validityPeriod.required`} + /> ); } diff --git a/nio-server/javascript/src/nio/services/ConsentService.js b/nio-server/javascript/src/nio/services/ConsentService.js index 254a46e4..b79aa538 100644 --- a/nio-server/javascript/src/nio/services/ConsentService.js +++ b/nio-server/javascript/src/nio/services/ConsentService.js @@ -1,6 +1,6 @@ export function getConsents(tenant, organisationKey, userId) { - return fetch(`/api/${tenant}/organisations/${organisationKey}/users/${userId}`, { + return fetch(`/api/${tenant}/organisations/${organisationKey}/users/${userId}?showExpiredConsents=true`, { method: "GET", credentials: 'include', headers: { diff --git a/nio-server/test/controllers/ConsentControllerSpec.scala b/nio-server/test/controllers/ConsentControllerSpec.scala index 67adfd0f..3174d4e7 100644 --- a/nio-server/test/controllers/ConsentControllerSpec.scala +++ b/nio-server/test/controllers/ConsentControllerSpec.scala @@ -3,7 +3,7 @@ package controllers import org.apache.pekko.japi.Option import models._ -import java.time.{Clock, LocalDateTime} +import java.time.{Clock, LocalDateTime, ZoneId} import utils.NioLogger import play.api.libs.json.{JsArray, JsObject, JsValue, Json} import play.api.libs.ws.WSResponse @@ -11,6 +11,7 @@ import utils.{DateUtils, TestUtils} import play.api.test.Helpers._ import java.time.format.DateTimeFormatter +import scala.concurrent.duration.DurationInt class ConsentControllerSpec extends TestUtils { @@ -230,6 +231,26 @@ class ConsentControllerSpec extends TestUtils { "ConsentController" should { val organisationKey: String = "maif" +// val organisation: Organisation = Organisation( +// key = organisationKey, +// label = "maif", +// groups = Seq( +// PermissionGroup( +// key = "maifNotifs", +// label = "J'accepte de recevoir par téléphone, mail et SMS des offres personnalisées du groupe MAIF", +// permissions = Seq( +// Permission(key = "phone", label = "Par contact téléphonique", validityPeriod = Some(1.minute)), +// Permission(key = "mail", label = "Par contact électronique"), +// Permission(key = "sms", label = "Par SMS / MMS / VMS") +// ) +// ) +// ) +// ) +// postJson(s"/$tenant/organisations", organisation.asJson()).status mustBe CREATED +// postJson(s"/$tenant/organisations/$organisationKey/draft/_release", organisation.asJson()).status mustBe OK +// postJson(s"/$tenant/organisations/$organisationKey/draft/_release", organisation.asJson()).status mustBe OK + + "user not exist" in { val path: String = s"/$tenant/organisations/$organisationKey/users/$userId1" @@ -297,6 +318,9 @@ class ConsentControllerSpec extends TestUtils { } "create user consents" in { + + println(getJson(s"/$tenant/organisations/$organisationKey").json) + val path: String = s"/$tenant/organisations/$organisationKey/users/$userId1" val putResponse = putJson(path, user1AsJson) @@ -336,9 +360,12 @@ class ConsentControllerSpec extends TestUtils { consents1.value.size mustBe 3 - (consents1 \ 0 \ "key").as[String] mustBe "phone" - (consents1 \ 0 \ "label").as[String] mustBe "Par contact téléphonique" - (consents1 \ 0 \ "checked").as[Boolean] mustBe true + val consent0 = consents1 \ 0 + (consent0 \ "key").as[String] mustBe "phone" + (consent0 \ "label").as[String] mustBe "Par contact téléphonique" + (consent0 \ "checked").as[Boolean] mustBe true + (consent0 \ "expiredAt").asOpt[LocalDateTime](DateUtils.utcDateTimeReads) + .map(_.withSecond(0)) mustBe Some(LocalDateTime.now(Clock.systemUTC()).plusHours(1).withSecond(0).withNano(0)) (consents1 \ 1 \ "key").as[String] mustBe "mail" (consents1 \ 1 \ "label").as[String] mustBe "Par contact électronique" @@ -809,15 +836,18 @@ class ConsentControllerSpec extends TestUtils { ) )) - println(patchResponse.json) + val json = patchResponse.json + println(json) patchResponse.status mustBe OK - val expectedDate: LocalDateTime = (patchResponse.json \ "lastUpdate").validate(DateUtils.utcDateTimeReads).get - patchResponse.json mustBe user1.copy( + val expectedDate: LocalDateTime = (json \ "lastUpdate").validate(DateUtils.utcDateTimeReads).get + json mustBe user1.copy( orgKey = Some("maif"), lastUpdate = expectedDate, groups = user1.groups.updated(0, user1.groups(0).copy( - consents = user1.groups(0).consents.updated(0, user1.groups(0).consents(0).copy(checked = false)) + consents = user1.groups(0).consents.updated(0, user1.groups(0).consents(0).copy( + checked = false, + expiredAt = Some(LocalDateTime.now(Clock.systemUTC()).plusHours(1).withNano(0)))) )) ).asJson() } diff --git a/nio-server/test/controllers/OrganisationControllerSpec.scala b/nio-server/test/controllers/OrganisationControllerSpec.scala index 6e126a60..98bb6190 100644 --- a/nio-server/test/controllers/OrganisationControllerSpec.scala +++ b/nio-server/test/controllers/OrganisationControllerSpec.scala @@ -9,6 +9,7 @@ import play.api.libs.ws.WSResponse import play.api.test.Helpers._ import java.time.format.DateTimeFormatter +import scala.concurrent.duration.DurationInt class OrganisationControllerSpec extends TestUtils { @@ -19,7 +20,10 @@ class OrganisationControllerSpec extends TestUtils { key = org1Key, label = "lbl", groups = Seq( - PermissionGroup(key = "group1", label = "blalba", permissions = Seq(Permission("sms", "Please accept sms"))) + PermissionGroup(key = "group1", label = "blalba", permissions = Seq( + Permission("sms", "Please accept sms"), + Permission("call", "Please accept call", validityPeriod = Some(1.hour)), + )) ) ) val org1AsJson = org1.asJson() @@ -116,10 +120,12 @@ class OrganisationControllerSpec extends TestUtils { (groups \ 0 \ "label").as[String] mustBe org1.groups.head.label val permissions = (groups \ 0 \ "permissions").as[JsArray] - (permissions \ 0 \ "key") - .as[String] mustBe org1.groups.head.permissions.head.key - (permissions \ 0 \ "label") - .as[String] mustBe org1.groups.head.permissions.head.label + (permissions \ 0 \ "key").as[String] mustBe org1.groups.head.permissions.head.key + (permissions \ 0 \ "label").as[String] mustBe org1.groups.head.permissions.head.label + (permissions \ 0 \ "validityPeriod").asOpt[String] mustBe None + (permissions \ 1 \ "key").as[String] mustBe "call" + (permissions \ 1 \ "label").as[String] mustBe "Please accept call" + (permissions \ 1 \ "validityPeriod").as[String] mustBe "1 hour" val getOrganisationsResponse: WSResponse = getJson(path) getOrganisationsResponse.status mustBe OK diff --git a/nio-server/test/models/ModelValidationSpec.scala b/nio-server/test/models/ModelValidationSpec.scala index de991795..5cf909fd 100644 --- a/nio-server/test/models/ModelValidationSpec.scala +++ b/nio-server/test/models/ModelValidationSpec.scala @@ -14,6 +14,7 @@ class ModelValidationSpec extends AnyWordSpecLike with Matchers { val now: LocalDateTime = LocalDateTime.now(Clock.systemUTC) "Validation ConsentFact" should { + val consentFact: ConsentFact = ConsentFact( _id = "1", userId = "user1", @@ -30,7 +31,8 @@ class ModelValidationSpec extends AnyWordSpecLike with Matchers { Consent( key = "g1c1", label = "group 1 consent 1", - checked = false + checked = false, + expiredAt = Some(LocalDateTime.now().plusMinutes(20)) ), Consent( key = "g1c2", diff --git a/nio-server/test/models/OrganisationSpec.scala b/nio-server/test/models/OrganisationSpec.scala index f919945c..584809a6 100644 --- a/nio-server/test/models/OrganisationSpec.scala +++ b/nio-server/test/models/OrganisationSpec.scala @@ -4,6 +4,9 @@ import org.scalatest.wordspec.AnyWordSpecLike import org.scalatestplus.play.PlaySpec import play.api.libs.json.Json +import java.util.concurrent.TimeUnit +import scala.concurrent.duration.FiniteDuration + class OrganisationSpec extends PlaySpec with AnyWordSpecLike { "Organisation" should { @@ -189,4 +192,69 @@ println(Json.prettyPrint(json)) fromJson.map(_.key) mustBe Right(org.key) } + + "serialize/deserialize from XML with validity" in { + val org = Organisation( + _id = "cf", + key = "maif", + label = "test org", + version = VersionInfo(), + groups = Seq( + PermissionGroup( + key = "a", + label = "a", + permissions = Seq( + Permission( + key = "a1", + label = "a1", + validityPeriod = Some(FiniteDuration(1, TimeUnit.DAYS)) + ), + Permission( + key = "a2", + label = "a2" + ) + ) + ) + ) + ) + + val xml = org.asXml() + + val fromXml = Organisation.fromXml(xml) + + fromXml.map(_.key) mustBe Right(org.key) + } + + "serialize/deserialize from JSON with validity" in { + val org = Organisation( + _id = "cf", + key = "maif", + label = "test org", + version = VersionInfo(), + groups = Seq( + PermissionGroup( + key = "a", + label = "a", + permissions = Seq( + Permission( + key = "a1", + label = "a1", + validityPeriod = Some(FiniteDuration(1, TimeUnit.DAYS)) + ), + Permission( + key = "a2", + label = "a2" + ) + ) + ) + ) + ) + + val json = org.asJson() + + val fromJson = Organisation.fromJson(json) + + fromJson.map(_.key) mustBe Right(org.key) + } + } diff --git a/nio-server/test/utils/TestUtils.scala b/nio-server/test/utils/TestUtils.scala index 9bf0c95d..6add3c1a 100644 --- a/nio-server/test/utils/TestUtils.scala +++ b/nio-server/test/utils/TestUtils.scala @@ -94,7 +94,7 @@ trait TestUtils |db.tenants=["$tenant"] |nio.filter.securityMode="default" """.stripMargin) - .resolve() + .resolve().withFallback(ConfigFactory.load("dataset.conf")) } protected lazy val authInfo: AuthInfoMock = new AuthInfoTest diff --git a/project/Dependencies.scala b/project/Dependencies.scala index e48ac623..21c79150 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,4 +1,7 @@ object Dependencies { + + val _scalaVersion = "2.13.15" + val reactiveMongoVersion = "1.1.0" val pekko = "1.1.1" val pekkoKafka = "1.1.0" diff --git a/project/plugins.sbt b/project/plugins.sbt index 800c8450..349304b2 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -13,4 +13,6 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0") // Apache 2.0 addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.13") // Apache 2.0 -ThisBuild / libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always \ No newline at end of file +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.13.0") + +ThisBuild / libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always diff --git a/version.sbt b/version.sbt index 53dfa333..eb2301f2 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "2.0.6-SNAPSHOT" +ThisBuild / version := "2.0.6-SNAPSHOT"