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

Formulaire d’inscription #1900

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
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
426 changes: 426 additions & 0 deletions app/controllers/AccountCreationController.scala

Large diffs are not rendered by default.

53 changes: 30 additions & 23 deletions app/controllers/GroupController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ import models.EventType.{
import modules.AppConfig
import scala.concurrent.{ExecutionContext, Future}
import serializers.Keys
import cats.data.EitherT

@Singleton
case class GroupController @Inject() (
accountCreationService: AccountCreationService,
config: AppConfig,
applicationService: ApplicationService,
loginAction: LoginAction,
Expand Down Expand Up @@ -410,35 +412,40 @@ case class GroupController @Inject() (
rights: Authorization.UserRights,
addUserForm: Form[AddUserToGroupFormData]
)(implicit request: RequestWithUserData[_]): Future[Result] =
for {
groups <- groupService.byIdsFuture(user.groupIds)
users <- userService.byGroupIdsFuture(groups.map(_.id), includeDisabled = true)
applications <- applicationService.allForUserIds(users.map(_.id), none)
lastActivityResult <- adminLastActivity(users.map(_.id), rights)
(for {
groups <- EitherT.right(groupService.byIdsFuture(user.groupIds))
users <- EitherT.right(userService.byGroupIdsFuture(groups.map(_.id), includeDisabled = true))
applications <- EitherT.right(applicationService.allForUserIds(users.map(_.id), none))
lastActivity <- EitherT(adminLastActivity(users.map(_.id), rights))
accountCreationForms <- EitherT(
accountCreationService
.listByAreasAndOrganisations(user.managingAreaIds, user.managingOrganisationIds)
)
} yield {
lastActivityResult.fold(
eventService.log(EventType.EditMyGroupShowed, "Visualise la modification de ses groupes")
Ok(
views.editMyGroups
.page(
user,
rights,
addUserForm,
groups,
users,
applications,
lastActivity.toMap,
accountCreationForms,
identity
)
)
}).value.map(
_.fold(
error => {
eventService.logError(error)
InternalServerError(Constants.genericError500Message)
},
lastActivity => {
eventService.log(EventType.EditMyGroupShowed, "Visualise la modification de ses groupes")
Ok(
views.editMyGroups
.page(
user,
rights,
addUserForm,
groups,
users,
applications,
lastActivity.toMap,
identity
)
)
}
identity
)
}
)

private def editGroupPage(group: UserGroup, addUserForm: Form[AddUserToGroupFormData])(implicit
request: RequestWithUserData[_]
Expand Down
17 changes: 15 additions & 2 deletions app/helper/TasksHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package helper

import cats.effect.IO
import cats.syntax.all._
import java.time.Instant
import java.time.{Instant, ZoneOffset}
import java.time.temporal.ChronoUnit
import models.{Error, EventType}
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration._
import services.EventService

trait TasksHelpers {
Expand Down Expand Up @@ -59,4 +60,16 @@ trait TasksHelpers {
def repeatWithDelay(delayUntilNextTick: Instant => IO[FiniteDuration])(task: IO[Unit]): IO[Unit] =
nextTick(delayUntilNextTick) >> task >> repeatWithDelay(delayUntilNextTick)(task)

def untilNextDayAt(hour: Int, minute: Int)(now: Instant): IO[FiniteDuration] = IO {
val nextInstant = now
.atZone(ZoneOffset.UTC)
.toLocalDate
.atStartOfDay(ZoneOffset.UTC)
.plusDays(1)
.withHour(hour)
.withMinute(minute)
.toInstant
now.until(nextInstant, ChronoUnit.MILLIS).millis
}

}
8 changes: 8 additions & 0 deletions app/helper/Time.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import cats.Order
import java.time.{Instant, LocalDate, ZoneId, ZonedDateTime}
import java.time.format.DateTimeFormatter
import java.util.Locale
import scala.concurrent.duration.FiniteDuration

object Time {

Expand Down Expand Up @@ -49,4 +50,11 @@ object Time {
override def compare(x: Instant, y: Instant): Int = x.compareTo(y)
}

def formatFiniteDuration(duration: FiniteDuration): String = {
val hours = duration.toHours
val minutes = duration.toMinutes % 60
val seconds = duration.toSeconds % 60
f"$hours%02d:$minutes%02d:$seconds%02d"
}

}
113 changes: 113 additions & 0 deletions app/models/AccountCreation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package models

import helper.StringHelper.withQuotes
import helper.Time
import java.time.Instant
import java.util.UUID

case class AccountCreationRequest(
id: UUID,
requestDate: Instant,
email: String,
isNamedAccount: Boolean,
firstName: String,
lastName: String,
phoneNumber: Option[String],
areaIds: List[UUID],
qualite: Option[String],
organisationId: Option[Organisation.Id],
miscOrganisation: Option[String],
// TODO: rename
isManager: Boolean,
isInstructor: Boolean,
message: Option[String],
fillingIpAddress: String,
rejectionUserId: Option[UUID],
rejectionDate: Option[Instant],
rejectionReason: Option[String]
) {

lazy val requestDateLog: String = Time.formatForAdmins(requestDate)
lazy val emailLog: String = withQuotes(email)
lazy val phoneNumberLog: String = phoneNumber.getOrElse("<vide>")
lazy val rejectionDateLog: String = rejectionDate.map(Time.formatForAdmins).getOrElse("<vide>")
lazy val rejectionReasonLog: String = rejectionReason.getOrElse("<vide>")

lazy val toLogString: String =
"[" + List[(String, String)](
("Id", id.toString),
("Date", requestDateLog),
("Email", emailLog),
("Compte nommé", isNamedAccount.toString),
("Prénom", firstName),
("Nom", lastName),
("Téléphone", phoneNumberLog),
("Territoires", areaIds.mkString(", ")),
("Qualité", qualite.getOrElse("")),
("Organisme", organisationId.map(_.id).getOrElse("<vide>")),
("Autre organisme", miscOrganisation.getOrElse("")),
("Responsable", isManager.toString),
("Instructeur", isInstructor.toString),
("Message", message.getOrElse("")),
("Adresse IP", fillingIpAddress),
("Utilisateur ayant rejeté", id.toString),
("Date de rejet", rejectionDateLog),
("Raison du rejet", rejectionReasonLog)
).map { case (fieldName, value) => s"$fieldName : $value" }.mkString(" | ") + "]"

}

case class AccountCreationSignature(
id: UUID,
formId: UUID,
firstName: String,
lastName: String,
phoneNumber: Option[String]
) {

lazy val phoneNumberLog: String = phoneNumber.getOrElse("<vide>")

lazy val toLogString: String =
"[" + List[(String, String)](
("Id", id.toString),
("Id du formulaire", formId.toString),
("Prénom", firstName),
("Nom", lastName),
("Téléphone", phoneNumberLog)
).map { case (fieldName, value) => s"$fieldName : $value" }.mkString(" | ") + "]"

}

case class AccountCreation(
form: AccountCreationRequest,
signatures: List[AccountCreationSignature]
) {

lazy val toLogString: String =
"[" + List[(String, String)](
("Form", form.toLogString),
("Signatures", signatures.map(_.toLogString).mkString(", "))
).map { case (fieldName, value) => s"$fieldName : $value" }.mkString(" | ") + "]"

}

object AccountCreationStats {

case class PeriodStats(
minCount: Int,
maxCount: Int,
median: Double,
quartile1: Double,
quartile3: Double,
percentile99: Double,
mean: Double,
stddev: Double,
)

}

case class AccountCreationStats(
todayCount: Int,
yearStats: AccountCreationStats.PeriodStats,
allStats: AccountCreationStats.PeriodStats,
)
6 changes: 6 additions & 0 deletions app/models/Authorization.scala
Original file line number Diff line number Diff line change
Expand Up @@ -366,4 +366,10 @@ object Authorization {
answerFileCanBeShowed(filesExpirationInDays)(application, answerId)(userId, rights)
}

def canManageAccountCreationForm(form: AccountCreationRequest): Check =
atLeastOneIsAuthorized(
isAdmin,
isAreaManager(form.areaIds.toSet, form.organisationId.toSet)
)

}
2 changes: 2 additions & 0 deletions app/models/EventType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,11 @@ object EventType {
object SignupFormValidationError extends Warn
object SignupFormError extends Error
object SignupFormSuccessful extends Info
object SignupFormRejected extends Info
object SignupsValidationError extends Warn
object SignupsUnauthorized extends Error
object SignupsError extends Error
object SignupsStatistics extends Info
object SignupEmailError extends Error
object SignupCreated extends Info

Expand Down
69 changes: 69 additions & 0 deletions app/models/formModels.scala
Original file line number Diff line number Diff line change
Expand Up @@ -537,4 +537,73 @@ object formModels {

}

case class AccountCreationFormData(
email: String,
isNamedAccount: Boolean,
firstName: String,
lastName: String,
phoneNumber: Option[String],
area: List[UUID],
qualite: Option[String],
organisation: Option[Organisation.Id],
miscOrganisation: Option[String],
isManager: Boolean,
isInstructor: Boolean,
message: Option[String],
signatures: List[AccountCreationFormData.Signature],
groups: List[UUID],
)

object AccountCreationFormData {

case class Signature(
firstName: String,
lastName: String,
phoneNumber: Option[String]
)

val signatureForm: Form[Signature] =
Form(
mapping(
"firstName" -> nonEmptyText,
"lastName" -> nonEmptyText,
"phoneNumber" -> optional(text)
)(Signature.apply)(Signature.unapply)
)

// TODO: add character constraints
// TODO: unicode
val form: Form[AccountCreationFormData] =
Form(
mapping(
"email" -> email,
"isNamedAccount" -> boolean,
"firstName" -> nonEmptyText,
"lastName" -> nonEmptyText,
"phoneNumber" -> optional(text),
// TODO: validate
"area" -> list(uuid),
"qualite" -> optional(text),
// TODO: validate
"organisation" -> optional(of[Organisation.Id]),
"miscOrganisation" -> optional(text),
"isManager" -> boolean,
"isInstructor" -> boolean,
"message" -> optional(text),
"signatures" -> list(signatureForm.mapping),
"groups" -> list(uuid),
)(AccountCreationFormData.apply)(AccountCreationFormData.unapply)
)

case class AccountType(isNamedAccount: Boolean)

val accountTypeForm: Form[AccountType] =
Form(
mapping(
"isNamedAccount" -> boolean,
)(AccountType.apply)(AccountType.unapply)
)

}

}
12 changes: 12 additions & 0 deletions app/modules/AppConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,16 @@ class AppConfig @Inject() (configuration: Configuration) {
val statisticsTimeToProcessApplicationsUrl: Option[String] =
configuration.getOptional[String]("app.statistics.timeToProcessApplicationsUrl")

val accountCreationAbuseThreshold: Int =
configuration.get[Int]("app.accountCreation.abuseThreshold")

val accountCreationAdminEmails: List[String] =
configuration
.get[String]("app.accountCreation.adminEmails")
.split(",")
.map(_.trim)
.filterNot(_.isEmpty)
.distinct
.toList

}
Loading