From 0e5468258cdfd9105297bc2321af74e04e41d0cf Mon Sep 17 00:00:00 2001 From: Charles Dufour Date: Tue, 30 Jan 2024 13:47:50 +0100 Subject: [PATCH] [TRELLO-2149] Implement create website manually endpoint (#1534) * [TRELLO-2149] Implement create website manually endpoint * [TRELLO-2149] Implement create website manually button --- app/controllers/WebsiteController.scala | 13 +++++ app/controllers/error/AppError.scala | 5 ++ app/models/website/WebsiteCreation.scala | 10 ++++ app/orchestrators/WebsitesOrchestrator.scala | 55 ++++++++++++++++--- .../website/WebsiteRepository.scala | 19 +++++++ .../website/WebsiteRepositoryInterface.scala | 4 ++ conf/routes | 1 + 7 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 app/models/website/WebsiteCreation.scala diff --git a/app/controllers/WebsiteController.scala b/app/controllers/WebsiteController.scala index d222e8f6..d1448885 100644 --- a/app/controllers/WebsiteController.scala +++ b/app/controllers/WebsiteController.scala @@ -18,6 +18,7 @@ import play.api.mvc.AnyContent import play.api.mvc.ControllerComponents import repositories.company.CompanyRepositoryInterface import authentication.actions.UserAction.WithRole +import utils.URL import java.time.OffsetDateTime import scala.concurrent.ExecutionContext @@ -36,6 +37,18 @@ class WebsiteController( implicit val timeout: akka.util.Timeout = 5.seconds val logger: Logger = Logger(this.getClass) + def create() = SecuredAction.andThen(WithRole(UserRole.Admin)).async(parse.json) { request => + request.body + .validate[WebsiteCreation] + .fold( + errors => Future.successful(BadRequest(JsError.toJson(errors))), + websiteCreation => + websitesOrchestrator + .create(URL(websiteCreation.host), websiteCreation.company.toCompany()) + .map(website => Ok(Json.toJson(website))) + ) + } + def fetchWithCompanies( maybeHost: Option[String], maybeIdentificationStatus: Option[Seq[IdentificationStatus]], diff --git a/app/controllers/error/AppError.scala b/app/controllers/error/AppError.scala index 6ec4924a..07bb848e 100644 --- a/app/controllers/error/AppError.scala +++ b/app/controllers/error/AppError.scala @@ -507,4 +507,9 @@ object AppError { override val titleForLogs: String = "different_user_from_request" } + final case class CreateWebsiteError(override val details: String) extends BadRequestError { + override val `type`: String = "SC-0054" + override val title: String = "Can't create website" + override val titleForLogs: String = "cant_create_website" + } } diff --git a/app/models/website/WebsiteCreation.scala b/app/models/website/WebsiteCreation.scala new file mode 100644 index 00000000..d2a708d1 --- /dev/null +++ b/app/models/website/WebsiteCreation.scala @@ -0,0 +1,10 @@ +package models.website + +import models.company.CompanyCreation +import play.api.libs.json.Json +import play.api.libs.json.Reads + +case class WebsiteCreation(host: String, company: CompanyCreation) +object WebsiteCreation { + implicit val reads: Reads[WebsiteCreation] = Json.reads[WebsiteCreation] +} diff --git a/app/orchestrators/WebsitesOrchestrator.scala b/app/orchestrators/WebsitesOrchestrator.scala index 790c5390..5b5c7082 100644 --- a/app/orchestrators/WebsitesOrchestrator.scala +++ b/app/orchestrators/WebsitesOrchestrator.scala @@ -3,6 +3,7 @@ package orchestrators import cats.implicits.catsSyntaxMonadError import cats.implicits.catsSyntaxOption import controllers.error.AppError.CannotDeleteWebsite +import controllers.error.AppError.CreateWebsiteError import controllers.error.AppError.MalformedHost import controllers.error.AppError.WebsiteHostIsAlreadyIdentified import controllers.error.AppError.WebsiteNotFound @@ -42,6 +43,26 @@ class WebsitesOrchestrator( val logger: Logger = Logger(this.getClass) + def create(url: URL, company: Company): Future[Website] = + for { + host <- url.getHost.liftTo[Future](MalformedHost(url.value)) + createdCompany <- companyRepository.getOrCreate(company.siret, company) + identified <- repository.listIdentified(host) + notAssociatedToCompany <- repository.listNotAssociatedToCompany(host) + _ <- + if (notAssociatedToCompany.nonEmpty || identified.flatMap(_.companyId).contains(createdCompany.id)) + Future.failed(CreateWebsiteError("Impossible de créer un site web s'il est déjà associé mais pas identifié")) + else + Future.unit + website = Website( + host = host, + companyCountry = None, + companyId = Some(createdCompany.id), + identificationStatus = IdentificationStatus.Identified + ) + createdWebsite <- repository.create(website) + } yield createdWebsite + def searchByHost(host: String): Future[Seq[Country]] = for { validHost <- URL(host).getHost.liftTo[Future](MalformedHost(host)) @@ -82,7 +103,14 @@ class WebsitesOrchestrator( newIdentificationStatus: IdentificationStatus, user: User ): Future[Website] = for { - website <- findWebsite(websiteId) + website <- findWebsite(websiteId) + identified <- repository.listIdentified(website.host) + _ <- + if (identified.length > 1) + Future.failed( + CreateWebsiteError("Le status ne peut pas être modifié si plus d'un siret est associé au site web") + ) + else Future.unit _ = if (website.companyCountry.isEmpty && website.companyId.isEmpty) { throw WebsiteNotIdentified(website.host) } @@ -102,7 +130,7 @@ class WebsitesOrchestrator( _ = logger.debug(s"Company Siret is ${maybeCompany.map(_.siret)}") _ <- maybeCompany .map(company => updatePreviousReportsAssociatedToWebsite(website.host, company, user.id)) - .getOrElse(Future.successful(())) + .getOrElse(Future.unit) } yield () } else Future.unit } yield updatedWebsite @@ -122,9 +150,16 @@ class WebsitesOrchestrator( for { company <- { logger.debug(s"Updating website (id ${websiteId}) with company siret : ${companyToAssign.siret}") - getOrCreateCompay(companyToAssign) + getOrCreateCompany(companyToAssign) } - website <- findWebsite(websiteId) + website <- findWebsite(websiteId) + identified <- repository.listIdentified(website.host) + _ <- + if (identified.length > 1 && identified.flatMap(_.companyId).contains(company.id)) { + Future.failed( + CreateWebsiteError("L'entreprise ne peut pas être modifiée si plus d'un siret est associé au site web") + ) + } else Future.unit websiteToUpdate = website.copy( companyCountry = None, companyId = Some(company.id) @@ -135,9 +170,14 @@ class WebsitesOrchestrator( def updateCompanyCountry(websiteId: WebsiteId, companyCountry: String, user: User): Future[WebsiteAndCompany] = for { website <- { - logger.debug(s"Updating website (id ${websiteId.value}) with company country : ${companyCountry}") + logger.debug(s"Updating website (id ${websiteId.value}) with company country : $companyCountry") findWebsite(websiteId) } + identified <- repository.listIdentified(website.host) + _ <- + if (identified.length > 1) + Future.failed(CreateWebsiteError("Le pays ne peut pas être modifié si plus d'un siret est associé au site web")) + else Future.unit websiteToUpdate = website.copy( companyCountry = Some(companyCountry), companyId = None @@ -162,10 +202,11 @@ class WebsitesOrchestrator( for { maybeWebsite <- repository.get(websiteId) website <- maybeWebsite.liftTo[Future](WebsiteNotFound(websiteId)) + identified <- repository.listIdentified(website.host) isWebsiteUnderInvestigation = website.investigationStatus != NotProcessed isWebsiteIdentified = website.identificationStatus == Identified _ <- - if (isWebsiteIdentified || isWebsiteUnderInvestigation) { + if (identified.length < 2 && (isWebsiteIdentified || isWebsiteUnderInvestigation)) { logger.debug(s"Cannot delete identified / under investigation website") Future.failed(CannotDeleteWebsite(website.host)) } else { @@ -184,7 +225,7 @@ class WebsitesOrchestrator( def listInvestigationStatus(): Seq[InvestigationStatus] = InvestigationStatus.values - private[this] def getOrCreateCompay(companyCreate: CompanyCreation): Future[Company] = companyRepository + private[this] def getOrCreateCompany(companyCreate: CompanyCreation): Future[Company] = companyRepository .getOrCreate( companyCreate.siret, companyCreate.toCompany() diff --git a/app/repositories/website/WebsiteRepository.scala b/app/repositories/website/WebsiteRepository.scala index 20661ab2..39e1cbd0 100644 --- a/app/repositories/website/WebsiteRepository.scala +++ b/app/repositories/website/WebsiteRepository.scala @@ -202,4 +202,23 @@ class WebsiteRepository( .result ) + def listNotAssociatedToCompany(host: String): Future[Seq[Website]] = + db.run( + table + .filter(_.host === host) + .filter(website => + website.companyCountry.isDefined || (website.identificationStatus inSet List( + IdentificationStatus.NotIdentified + )) + ) + .result + ) + + def listIdentified(host: String): Future[Seq[Website]] = + db.run( + table + .filter(_.host === host) + .filter(_.identificationStatus inSet List(IdentificationStatus.Identified)) + .result + ) } diff --git a/app/repositories/website/WebsiteRepositoryInterface.scala b/app/repositories/website/WebsiteRepositoryInterface.scala index 5893379f..0246272e 100644 --- a/app/repositories/website/WebsiteRepositoryInterface.scala +++ b/app/repositories/website/WebsiteRepositoryInterface.scala @@ -49,4 +49,8 @@ trait WebsiteRepositoryInterface extends TypedCRUDRepositoryInterface[Website, W start: Option[LocalDate] = None, end: Option[LocalDate] = None ): Future[List[(String, Int)]] + + def listNotAssociatedToCompany(host: String): Future[Seq[Website]] + + def listIdentified(host: String): Future[Seq[Website]] } diff --git a/conf/routes b/conf/routes index 5cee2e78..079a3d31 100644 --- a/conf/routes +++ b/conf/routes @@ -171,6 +171,7 @@ GET /api/resources/investigation-status controll POST /api/website-investigations controllers.WebsiteController.updateInvestigation() # Websites API GET /api/websites controllers.WebsiteController.fetchWithCompanies(host: Option[String], identificationStatus: Option[Seq[IdentificationStatus]], offset: Option[Long], limit: Option[Int],investigationStatus: Option[Seq[InvestigationStatus]],start: Option[OffsetDateTime],end: Option[OffsetDateTime],hasAssociation: Option[Boolean],isOpen: Option[Boolean]) +POST /api/websites controllers.WebsiteController.create() GET /api/websites/search-url controllers.WebsiteController.searchByHost(url: String) GET /api/websites/unregistered controllers.WebsiteController.fetchUnregisteredHost(q: Option[String], start: Option[String], end: Option[String]) GET /api/websites/unregistered/extract controllers.WebsiteController.extractUnregisteredHost(q: Option[String], start: Option[String], end: Option[String])