diff --git a/build.sbt b/build.sbt index 665e2d408830..1d318a4a1996 100644 --- a/build.sbt +++ b/build.sbt @@ -210,6 +210,10 @@ val rss = application("rss") .dependsOn(commonWithTests) .aggregate(common) +val search = application("search") + .dependsOn(commonWithTests) + .aggregate(common) + val main = root() // This evicts the version of // "com.fasterxml.jackson.core:jackson-databind" @@ -235,6 +239,7 @@ val main = root() archive, preview, rss, + search, ) .settings( riffRaffUploadArtifactBucket := Some( diff --git a/riff-raff.yaml b/riff-raff.yaml index bb9032fca1d3..9ddaa18ebcaa 100644 --- a/riff-raff.yaml +++ b/riff-raff.yaml @@ -43,6 +43,8 @@ deployments: template: frontend sport: template: frontend + search: + template: frontend frontend-static: type: aws-s3 parameters: diff --git a/search/app/AppLoader.scala b/search/app/AppLoader.scala new file mode 100644 index 000000000000..595033b9b0f8 --- /dev/null +++ b/search/app/AppLoader.scala @@ -0,0 +1,70 @@ +import akka.actor.ActorSystem +import http.{CommonFilters, CorsHttpErrorHandler} +import app.{FrontendApplicationLoader, FrontendBuildInfo, FrontendComponents} +import com.softwaremill.macwire._ +import common._ +import common.Logback.{LogbackOperationsPool, LogstashLifecycle} +import contentapi.{CapiHttpClient, ContentApiClient, HttpClient} +import controllers.{HealthCheck, SearchController} +import dev.{DevAssetsController, DevParametersHttpRequestHandler} +import model.{ApplicationContext, ApplicationIdentity} +import play.api.ApplicationLoader.Context +import play.api.http.{HttpErrorHandler, HttpRequestHandler} +import play.api.{BuiltInComponentsFromContext, Environment} +import play.api.mvc.{ControllerComponents, EssentialFilter} +import play.api.routing.Router +import play.api.libs.ws.WSClient +import router.Routes + +import scala.concurrent.ExecutionContext + +class AppLoader extends FrontendApplicationLoader { + override def buildComponents(context: Context): FrontendComponents = + new BuiltInComponentsFromContext(context) with AppComponents +} + +trait SearchControllers { + def contentApiClient: ContentApiClient + def wsClient: WSClient + def controllerComponents: ControllerComponents + implicit def appContext: ApplicationContext + lazy val searchController = wire[SearchController] +} + +trait SearchServices { + def wsClient: WSClient + def environment: Environment + def actorSystem: ActorSystem + implicit def appContext: ApplicationContext + implicit val executionContext: ExecutionContext + lazy val capiHttpClient: HttpClient = wire[CapiHttpClient] + lazy val contentApiClient = wire[ContentApiClient] +} + +trait AppComponents extends FrontendComponents with SearchControllers with SearchServices { + + lazy val healthCheck = wire[HealthCheck] + lazy val devAssetsController = wire[DevAssetsController] + lazy val logbackOperationsPool = wire[LogbackOperationsPool] + + override lazy val lifecycleComponents = List( + wire[LogstashLifecycle], + ) + + lazy val router: Router = wire[Routes] + + lazy val appIdentity = ApplicationIdentity("search") + + val applicationMetrics = ApplicationMetrics( + ContentApiMetrics.HttpTimeoutCountMetric, + ContentApiMetrics.ContentApiErrorMetric, + ContentApiMetrics.ContentApiRequestsMetric, + ) + + val frontendBuildInfo: FrontendBuildInfo = frontend.search.BuildInfo + override lazy val httpFilters: Seq[EssentialFilter] = wire[CommonFilters].filters + override lazy val httpRequestHandler: HttpRequestHandler = wire[DevParametersHttpRequestHandler] + override lazy val httpErrorHandler: HttpErrorHandler = wire[CorsHttpErrorHandler] + + def actorSystem: ActorSystem +} diff --git a/search/app/controllers/HealthCheck.scala b/search/app/controllers/HealthCheck.scala new file mode 100644 index 000000000000..139a8083b7d2 --- /dev/null +++ b/search/app/controllers/HealthCheck.scala @@ -0,0 +1,13 @@ +package controllers + +import conf.{AllGoodCachedHealthCheck, NeverExpiresSingleHealthCheck} +import play.api.libs.ws.WSClient +import play.api.mvc.ControllerComponents + +import scala.concurrent.ExecutionContext + +class HealthCheck(wsClient: WSClient, val controllerComponents: ControllerComponents)(implicit + executionContext: ExecutionContext, +) extends AllGoodCachedHealthCheck( + NeverExpiresSingleHealthCheck("/search"), + )(wsClient, executionContext) diff --git a/search/app/controllers/SearchController.scala b/search/app/controllers/SearchController.scala new file mode 100644 index 000000000000..3bd3a895be3b --- /dev/null +++ b/search/app/controllers/SearchController.scala @@ -0,0 +1,23 @@ +package controllers + +import common.{GuLogging, ImplicitControllerExecutionContext} +import contentapi.ContentApiClient +import model.ApplicationContext +import play.api.libs.ws.WSClient +import play.api.mvc.{BaseController, ControllerComponents} + +import scala.concurrent.Future + +class SearchController( + contentApiClient: ContentApiClient, + ws: WSClient, + val controllerComponents: ControllerComponents, +)(implicit context: ApplicationContext) + extends BaseController + with GuLogging + with ImplicitControllerExecutionContext { + + def search() = { + Action.async(Future.successful(Ok("ok"))) + } +} diff --git a/search/conf/application.conf b/search/conf/application.conf new file mode 100644 index 000000000000..59884f34db9a --- /dev/null +++ b/search/conf/application.conf @@ -0,0 +1,52 @@ + +akka { + loggers = ["akka.event.Logging$DefaultLogger", "akka.event.slf4j.Slf4jLogger"] + loglevel = WARNING + actor { + default-dispatcher = { + fork-join-executor { + parallelism-factor = 1.0 + parallelism-max = 24 + } + } + java-futures = { + fork-join-executor { + parallelism-factor = 1.0 + parallelism-max = 1 + } + } + } +} + +play { + + http { + # The secret key is used to secure cryptographics functions. + # If you deploy your application to several instances be sure to use the same key! + secret.key: ${APP_SECRET} + forwarded.trustedProxies = [ "0.0.0.0/0" ] + } + + il8n { + langs: "en" + } + + ws { + compressionEnabled: true + + timeout { + #The maximum time to wait when connecting to the remote host (default is 120 seconds). + connection: 1000ms + #The maximum time the request can stay idle (connection is established but waiting for more data) (default is 120 seconds). + idle: 2000ms + #The total time you accept a request to take (it will be interrupted even if the remote host is still sending data) (default is 120 seconds). + request: 2000ms + } + } + + application.loader = AppLoader +} + +guardian: { + projectName: search +} diff --git a/search/conf/logback.xml b/search/conf/logback.xml new file mode 100644 index 000000000000..db5e4bcd4fcd --- /dev/null +++ b/search/conf/logback.xml @@ -0,0 +1,22 @@ + + + frontend-onward + + + logs/frontend-onward.log + + + logs/frontend-onward.log.%d{yyyy-MM-dd}.%i.gz + 7512MB256MB + + + + %date [%thread] %-5level %logger{36} - %msg%n%xException + + + + + + + + diff --git a/search/conf/routes b/search/conf/routes new file mode 100644 index 000000000000..9b40bb79b8fd --- /dev/null +++ b/search/conf/routes @@ -0,0 +1,2 @@ +GET /healthcheck controllers.HealthCheck.healthCheck() +GET /search controllers.SearchController.search()