Skip to content

Commit

Permalink
Bookmarks table and api
Browse files Browse the repository at this point in the history
  • Loading branch information
eletallbetagouv committed Oct 30, 2024
1 parent 031083d commit ea3b66e
Show file tree
Hide file tree
Showing 39 changed files with 456 additions and 169 deletions.
2 changes: 1 addition & 1 deletion app/controllers/AdminController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ class AdminController(
SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async { implicit request =>
for {
reports <- reportRepository.getReportsWithFiles(
Some(request.identity.userRole),
Some(request.identity),
ReportFilter(start = Some(start), end = Some(end))
)
_ <- emailType match {
Expand Down
6 changes: 5 additions & 1 deletion app/controllers/BaseController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ abstract class BaseController(
) extends AbstractController(controllerComponents) {
implicit val ec: ExecutionContext

private def ipRateLimitFilter[F[_] <: Request[_]](size: Long, rate: Double): IpRateLimitFilter[F] =
private def ipRateLimitFilter[F[_] <: Request[_]](
size: Long,
// refill rate, in number of token per second
rate: Double
): IpRateLimitFilter[F] =
new IpRateLimitFilter[F](new RateLimiter(size, rate, "Rate limit by IP address")) {
override def rejectResponse[A](implicit request: F[A]): Future[Result] =
Future.successful(TooManyRequests(s"""Rate limit exceeded"""))
Expand Down
43 changes: 43 additions & 0 deletions app/controllers/BookmarkController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package controllers

import authentication.Authenticator
import authentication.actions.UserAction.WithRole
import models._
import orchestrators._
import play.api.Logger
import play.api.libs.json.Json
import play.api.mvc.ControllerComponents

import java.util.UUID
import scala.concurrent.ExecutionContext

class BookmarkController(
bookmarkOrchestrator: BookmarkOrchestrator,
authenticator: Authenticator[User],
controllerComponents: ControllerComponents
)(implicit val ec: ExecutionContext)
extends BaseController(authenticator, controllerComponents) {

val logger: Logger = Logger(this.getClass)

def addBookmark(uuid: UUID) =
SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { request =>
for {
_ <- bookmarkOrchestrator.addBookmark(uuid, request.identity)
} yield Ok
}

def removeBookmark(uuid: UUID) =
SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { request =>
for {
_ <- bookmarkOrchestrator.removeBookmark(uuid, request.identity)
} yield Ok
}

def countBookmarks() = SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { request =>
for {
count <- bookmarkOrchestrator.countBookmarks(request.identity)
} yield Ok(Json.toJson(count))
}

}
2 changes: 1 addition & 1 deletion app/controllers/CompanyController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class CompanyController(

def getResponseRate(companyId: UUID) = SecuredAction.async { request =>
companyOrchestrator
.getCompanyResponseRate(companyId, request.identity.userRole)
.getCompanyResponseRate(companyId, request.identity)
.map(results => Ok(Json.toJson(results)))
}

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/EventsController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class EventsController(
SecuredAction.async { implicit request =>
logger.info(s"Fetching events for report $reportId with eventType $eventType")
eventsOrchestrator
.getReportsEvents(reportId = reportId, eventType = eventType, userRole = request.identity.userRole)
.getReportsEvents(reportId = reportId, eventType = eventType, user = request.identity)
.map(events => Ok(Json.toJson(events)))
}

Expand Down
7 changes: 4 additions & 3 deletions app/controllers/ReportController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class ReportController(
SecuredAction.andThen(WithRole(UserRole.EveryoneButReadOnlyAdmin)).async(parse.json) { implicit request =>
for {
reportAction <- request.parseBody[ReportAction]()
reportWithMetadata <- reportRepository.getFor(Some(request.identity.userRole), uuid)
reportWithMetadata <- reportRepository.getFor(Some(request.identity), uuid)
report = reportWithMetadata.map(_.report)
newEvent <-
report
Expand Down Expand Up @@ -166,6 +166,7 @@ class ReportController(
ReportWithFilesAndAssignedUser(
r.report,
r.metadata,
r.bookmark.isDefined,
maybeAssignedMinimalUser,
reportFiles.map(ReportFileApi.build(_))
)
Expand Down Expand Up @@ -266,14 +267,14 @@ class ReportController(
def generateConsumerReportEmailAsPDF(uuid: UUID) =
SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)).async { implicit request =>
for {
maybeReportWithMetadata <- reportRepository.getFor(Some(request.identity.userRole), uuid)
maybeReportWithMetadata <- reportRepository.getFor(Some(request.identity), uuid)
maybeReport = maybeReportWithMetadata.map(_.report)
company <- maybeReport.flatMap(_.companyId).flatTraverse(r => companyRepository.get(r))
files <- reportFileRepository.retrieveReportFiles(uuid)
events <- eventsOrchestrator.getReportsEvents(
reportId = uuid,
eventType = None,
userRole = request.identity.userRole
user = request.identity
)
proResponseEvent = events.find(_.data.action == REPORT_PRO_RESPONSE)
source = maybeReport
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/ReportToExternalController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import models.Consumer
import models.PaginatedResult.paginatedResultWrites
import models.report._
import models.report.ReportWithFilesToExternal.format
import models.report.reportmetadata.ReportWithMetadata
import models.report.reportmetadata.ReportWithMetadataAndBookmark
import orchestrators.ReportOrchestrator
import play.api.Logger
import play.api.libs.json.Json
Expand Down Expand Up @@ -100,7 +100,7 @@ class ReportToExternalController(
offset,
limit,
signalConsoConfiguration.reportsListLimitMax,
(r: ReportWithMetadata, m: Map[UUID, List[ReportFile]]) =>
(r: ReportWithMetadataAndBookmark, m: Map[UUID, List[ReportFile]]) =>
ReportWithFilesToExternal(
ReportToExternal.fromReport(r.report),
m.getOrElse(r.report.id, Nil).map(ReportFileToExternal.fromReportFile)
Expand Down
18 changes: 9 additions & 9 deletions app/controllers/StatisticController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class StatisticController(
},
filters =>
statsOrchestrator
.getReportCount(Some(request.identity.userRole), filters)
.getReportCount(Some(request.identity), filters)
.map(count => Ok(Json.obj("value" -> count)))
)
}
Expand All @@ -65,7 +65,7 @@ class StatisticController(
.flatMap(CurveTickDuration.namesToValuesMap.get)
.getOrElse(CurveTickDuration.Month)
statsOrchestrator
.getReportsCountCurve(Some(request.identity.userRole), filters, ticks, tickDuration)
.getReportsCountCurve(Some(request.identity), filters, ticks, tickDuration)
.map(curve => Ok(Json.toJson(curve)))
}
)
Expand Down Expand Up @@ -114,17 +114,17 @@ class StatisticController(
}

def getReportsTagsDistribution(companyId: Option[UUID]) = SecuredAction.async { request =>
statsOrchestrator.getReportsTagsDistribution(companyId, request.identity.userRole).map(x => Ok(Json.toJson(x)))
statsOrchestrator.getReportsTagsDistribution(companyId, request.identity).map(x => Ok(Json.toJson(x)))
}

def getReportsStatusDistribution(companyId: Option[UUID]) = SecuredAction.async { request =>
statsOrchestrator.getReportsStatusDistribution(companyId, request.identity.userRole).map(x => Ok(Json.toJson(x)))
statsOrchestrator.getReportsStatusDistribution(companyId, request.identity).map(x => Ok(Json.toJson(x)))
}

def getAcceptedResponsesDistribution(companyId: UUID) =
SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { request =>
statsOrchestrator
.getAcceptedResponsesDistribution(companyId, request.identity.userRole)
.getAcceptedResponsesDistribution(companyId, request.identity)
.map(x => Ok(Json.toJson(x)))
}

Expand All @@ -136,13 +136,13 @@ class StatisticController(
visibleToPro = Some(true)
)
statsOrchestrator
.getReportsCountCurve(Some(request.identity.userRole), filter)
.getReportsCountCurve(Some(request.identity), filter)
.map(curve => Ok(Json.toJson(curve)))
}

def getProReportTransmittedStat() = SecuredAction.async { request =>
statsOrchestrator
.getReportsCountCurve(Some(request.identity.userRole), transmittedReportsFilter)
.getReportsCountCurve(Some(request.identity), transmittedReportsFilter)
.map(curve => Ok(Json.toJson(curve)))
}

Expand All @@ -154,7 +154,7 @@ class StatisticController(
.getOrElse(statusWithProResponse)
val filter = ReportFilter(status = statusFilter)
statsOrchestrator
.getReportsCountCurve(Some(request.identity.userRole), filter)
.getReportsCountCurve(Some(request.identity), filter)
.map(curve => Ok(Json.toJson(curve)))
}

Expand Down Expand Up @@ -190,7 +190,7 @@ class StatisticController(
Future.failed(MalformedQueryParams)
case Success(filters) =>
statsOrchestrator
.reportsCountBySubcategories(request.identity.userRole, filters)
.reportsCountBySubcategories(request.identity, filters)
.map(res => Ok(Json.toJson(res)))
}
}
Expand Down
7 changes: 7 additions & 0 deletions app/controllers/error/AppError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -586,4 +586,11 @@ object AppError {
override val titleForLogs: String = "user_not_found"
}

final case class InvalidFilters(explanation: String) extends BadRequestError {
override val scErrorCode: String = "SC-0063"
override val title: String = s"Invalid filters, $explanation"
override val details: String = title
override val titleForLogs: String = "invalid_filters"
}

}
9 changes: 9 additions & 0 deletions app/loader/SignalConsoApplicationLoader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import actors._
import org.apache.pekko.actor.typed
import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorSystemOps
import org.apache.pekko.util.Timeout
import authentication.CookieAuthenticator
import authentication._
import com.amazonaws.auth.AWSStaticCredentialsProvider
import com.amazonaws.auth.BasicAWSCredentials
Expand Down Expand Up @@ -49,6 +50,8 @@ import repositories.authtoken.AuthTokenRepositoryInterface
import repositories.barcode.BarcodeProductRepository
import repositories.blacklistedemails.BlacklistedEmailsRepository
import repositories.blacklistedemails.BlacklistedEmailsRepositoryInterface
import repositories.bookmark.BookmarkRepository
import repositories.bookmark.BookmarkRepositoryInterface
import repositories.company.CompanyRepository
import repositories.company.CompanyRepositoryInterface
import repositories.company.CompanySyncRepository
Expand Down Expand Up @@ -220,6 +223,7 @@ class SignalConsoComponents(
val influencerRepository: InfluencerRepositoryInterface = new InfluencerRepository(dbConfig)
def reportRepository: ReportRepositoryInterface = new ReportRepository(dbConfig)
val reportMetadataRepository: ReportMetadataRepositoryInterface = new ReportMetadataRepository(dbConfig)
val bookmarkRepository: BookmarkRepositoryInterface = new BookmarkRepository(dbConfig)
val reportNotificationBlockedRepository: ReportNotificationBlockedRepositoryInterface =
new ReportNotificationBlockedRepository(dbConfig)
val responseConsumerReviewRepository: ResponseConsumerReviewRepositoryInterface =
Expand Down Expand Up @@ -406,6 +410,8 @@ class SignalConsoComponents(
reportEngagementReviewRepository
)

val bookmarkOrchestrator = new BookmarkOrchestrator(reportRepository, bookmarkRepository)

val emailNotificationOrchestrator = new EmailNotificationOrchestrator(mailService, subscriptionRepository)

private def buildReportOrchestrator(emailService: MailServiceInterface) = new ReportOrchestrator(
Expand Down Expand Up @@ -843,6 +849,8 @@ class SignalConsoComponents(
val engagementController =
new EngagementController(engagementOrchestrator, cookieAuthenticator, controllerComponents)

val bookmarkController = new BookmarkController(bookmarkOrchestrator, cookieAuthenticator, controllerComponents)

io.sentry.Sentry.captureException(
new Exception("This is a test Alert, used to check that Sentry alert are still active on each new deployments.")
)
Expand All @@ -860,6 +868,7 @@ class SignalConsoComponents(
reportController,
reportConsumerReviewController,
eventsController,
bookmarkController,
reportToExternalController,
dataEconomieController,
adminController,
Expand Down
3 changes: 3 additions & 0 deletions app/models/report/Report.scala
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ object WebsiteURL {
case class ReportWithFiles(
report: Report,
metadata: Option[ReportMetadata],
isBookmarked: Boolean,
files: List[ReportFile]
)

Expand All @@ -170,6 +171,7 @@ case class EventWithUser(event: Event, user: Option[User])
case class ReportWithFilesAndAssignedUser(
report: Report,
metadata: Option[ReportMetadata],
isBookmarked: Boolean,
assignedUser: Option[MinimalUser],
files: List[ReportFileApi]
)
Expand All @@ -181,6 +183,7 @@ object ReportWithFilesAndAssignedUser {
case class ReportWithFilesAndResponses(
report: Report,
metadata: Option[ReportMetadata],
isBookmarked: Boolean,
assignedUser: Option[MinimalUser],
files: List[ReportFile],
consumerReview: Option[ResponseConsumerReview],
Expand Down
10 changes: 7 additions & 3 deletions app/models/report/ReportFilter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ case class ReportFilter(
visibleToPro: Option[Boolean] = None,
isForeign: Option[Boolean] = None,
hasBarcode: Option[Boolean] = None,
assignedUserId: Option[UUID] = None
assignedUserId: Option[UUID] = None,
isBookmarked: Option[Boolean] = None
)

object ReportFilter {
Expand Down Expand Up @@ -86,7 +87,8 @@ object ReportFilter {
fullText = mapper.string("fullText", trimmed = true),
isForeign = mapper.boolean("isForeign"),
hasBarcode = mapper.boolean("hasBarcode"),
subcategories = mapper.seq("subcategories")
subcategories = mapper.seq("subcategories"),
isBookmarked = mapper.boolean("isBookmarked")
)
}

Expand Down Expand Up @@ -123,6 +125,7 @@ object ReportFilter {
activityCodes <- (jsValue \ "activityCodes").validateOpt[Seq[String]]
isForeign <- (jsValue \ "isForeign").validateOpt[Boolean]
hasBarcode <- (jsValue \ "hasBarcode").validateOpt[Boolean]
isBookmarked <- (jsValue \ "isBookmarked").validateOpt[Boolean]
} yield ReportFilter(
departments = departments.getOrElse(Seq.empty),
email = email,
Expand Down Expand Up @@ -151,7 +154,8 @@ object ReportFilter {
withoutTags = withoutTags.getOrElse(Seq.empty).map(ReportTag.withName),
activityCodes = activityCodes.getOrElse(Seq.empty),
isForeign = isForeign,
hasBarcode = hasBarcode
hasBarcode = hasBarcode,
isBookmarked = isBookmarked
)
}

Expand Down
29 changes: 29 additions & 0 deletions app/models/report/reportmetadata/ReportMetadata.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import models.report.Report
import play.api.libs.json.Json
import play.api.libs.json.OWrites
import play.api.libs.json.Writes
import repositories.bookmark.Bookmark

import java.util.UUID

Expand Down Expand Up @@ -36,7 +37,35 @@ object ReportWithMetadata {
ReportWithMetadata(report, metadata)
}

def fromTuple(tuple: (Report, Option[ReportMetadata], Any)) = {
val (report, metadata, _) = tuple
ReportWithMetadata(report, metadata)
}

implicit def writes(implicit userRole: Option[UserRole]): OWrites[ReportWithMetadata] =
Json.writes[ReportWithMetadata]

}

case class ReportWithMetadataAndBookmark(
report: Report,
metadata: Option[ReportMetadata],
bookmark: Option[Bookmark]
) {
def setAddress(companyAddress: Address) =
this.copy(
report = this.report.copy(companyAddress = companyAddress)
)
}
object ReportWithMetadataAndBookmark {
def fromTuple(tuple: (Report, Option[ReportMetadata], Option[Bookmark])) = {
val (report, metadata, bookmark) = tuple
ReportWithMetadataAndBookmark(report, metadata, bookmark)
}

def fromTuple(tuple: (Report, Option[ReportMetadata], Option[Bookmark], Any)) = {
val (report, metadata, bookmark, _) = tuple
ReportWithMetadataAndBookmark(report, metadata, bookmark)
}

}
Loading

0 comments on commit ea3b66e

Please sign in to comment.