Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MEP [TRELLO-2056] Implement mass agent import (#1471) #1473

Merged
merged 1 commit into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions app/controllers/AccountController.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package controllers

import cats.implicits.catsSyntaxOption
import com.mohiva.play.silhouette.api.LoginEvent
import com.mohiva.play.silhouette.api.LoginInfo
import com.mohiva.play.silhouette.api.Silhouette
Expand All @@ -15,10 +16,12 @@ import repositories.user.UserRepositoryInterface
import utils.EmailAddress
import utils.silhouette.auth.AuthEnv
import utils.silhouette.auth.WithPermission
import error.AppError.MalformedFileKey

import java.util.UUID
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.io.Source

class AccountController(
val silhouette: Silhouette[AuthEnv],
Expand Down Expand Up @@ -73,6 +76,18 @@ class AccountController(
}
}

def sendAgentsInvitations(role: UserRole) =
SecuredAction(WithPermission(UserPermission.manageAdminOrAgentUsers)).async(parse.multipartFormData) {
implicit request =>
for {
filePart <- request.body.file("emails").liftTo[Future](MalformedFileKey("emails"))
source = Source.fromFile(filePart.ref.path.toFile)
lines = source.getLines().toList
_ = source.close()
_ <- accessesOrchestrator.sendAgentsInvitations(role, lines)
} yield Ok
}

def sendAdminInvitation = SecuredAction(WithPermission(UserPermission.manageAdminOrAgentUsers)).async(parse.json) {
implicit request =>
request
Expand Down
8 changes: 5 additions & 3 deletions app/controllers/error/AppError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,13 @@ object AppError {
override val titleForLogs: String = "malformed_website_host"
}

final case class InvalidDGCCRFOrAdminEmail(email: EmailAddress) extends ForbiddenError {
final case class InvalidDGCCRFOrAdminEmail(emails: List[EmailAddress]) extends ForbiddenError {
override val `type`: String = "SC-0008"
override val title: String = "Invalid email for this type of user"
override val details: String =
s"Email ${email.value} invalide pour ce type d'utilisateur"
override val details: String = emails match {
case email :: Nil => s"Email ${email.value} invalide pour ce type d'utilisateur"
case _ => s"""Emails ${emails.mkString("[", ",", "]")} invalides pour ce type d'utilisateur"""
}
override val titleForLogs: String = "invalid_email_for_admin_or_dgccrf"
}

Expand Down
43 changes: 41 additions & 2 deletions app/orchestrators/AccessesOrchestrator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,42 @@ class AccessesOrchestrator(
.withFieldConst(_.emailedTo, emailTo)
.transform

private def parseEmails(emails: List[String]): List[EmailAddress] =
emails
.flatMap(email => email.split(",").toList)
.map(_.trim)
.filter(_.nonEmpty)
.map(email => EmailAddress(email))

private def validateEmails(emails: List[EmailAddress], validateFunction: String => Boolean) =
if (emails.isEmpty)
Future.failed(InvalidDGCCRFOrAdminEmail(List.empty))
else {
val invalidEmails = emails.filter(email => !validateFunction(email.value))
if (invalidEmails.isEmpty) {
Future.successful(())
} else {
Future.failed(InvalidDGCCRFOrAdminEmail(invalidEmails))
}
}

def sendAgentsInvitations(role: UserRole, emails: List[String]): Future[List[Unit]] =
role match {
case UserRole.DGCCRF =>
val parsedEmails = parseEmails(emails)
for {
_ <- validateEmails(parsedEmails, EmailAddressService.isEmailAcceptableForDgccrfAccount)
res <- Future.sequence(parsedEmails.map(sendDGCCRFInvitation))
} yield res
case UserRole.DGAL =>
val parsedEmails = parseEmails(emails)
for {
_ <- validateEmails(parsedEmails, EmailAddressService.isEmailAcceptableForDgalAccount)
res <- Future.sequence(parsedEmails.map(sendDGALInvitation))
} yield res
case _ => Future.failed(WrongUserRole(role))
}

def sendDGCCRFInvitation(email: EmailAddress): Future[Unit] =
sendAdminOrAgentInvitation(email, TokenKind.DGCCRFAccount)

Expand Down Expand Up @@ -146,11 +182,14 @@ class AccessesOrchestrator(
if (emailValidationFunction(email.value)) {
Future.successful(())
} else {
Future.failed(InvalidDGCCRFOrAdminEmail(email))
Future.failed(InvalidDGCCRFOrAdminEmail(List(email)))
}
_ <- userOrchestrator.find(email).ensure(UserAccountEmailAlreadyExist)(_.isEmpty)
existingTokens <- accessTokenRepository.fetchPendingTokens(email)
existingToken = existingTokens.find(_.kind == kind)
existingToken = kind match {
case AdminAccount => existingTokens.find(_.kind == AdminAccount)
case DGALAccount | DGCCRFAccount => existingTokens.find(t => t.kind == DGCCRFAccount || t.kind == DGALAccount)
}
token <-
existingToken match {
case Some(token) =>
Expand Down
2 changes: 1 addition & 1 deletion app/orchestrators/ReportOrchestrator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class ReportOrchestrator(
.map(accessTokenRepository.updateToken(_, AccessLevel.ADMIN, validity))
.getOrElse(Future(None))
token <- existingToken
.map(Future(_))
.map(Future.successful)
.getOrElse(
accessTokenRepository.create(
AccessToken.build(
Expand Down
1 change: 1 addition & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ GET /api/account/token controll
POST /api/account/activation controllers.AccountController.activateAccount()
POST /api/account/admin/invitation controllers.AccountController.sendAdminInvitation()
POST /api/account/agent/invitation controllers.AccountController.sendAgentInvitation(role: UserRole)
POST /api/account/agent/invitations controllers.AccountController.sendAgentsInvitations(role: UserRole)
GET /api/account/agent/pending controllers.AccountController.fetchPendingAgent(role: Option[UserRole])
GET /api/account/admin-or-agent/users controllers.AccountController.fetchAdminOrAgentUsers()
GET /api/account/all/deleted-users controllers.AccountController.fetchAllSoftDeletedUsers()
Expand Down