diff --git a/.scalafmt.conf b/.scalafmt.conf index 702f9f99..a281cdc6 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -6,7 +6,7 @@ maxColumn = 110 spaces.inImportCurlyBraces = true rewrite.rules = [SortModifiers] rewrite.redundantBraces.stringInterpolation = true -rewrite.rules = [AvoidInfix] + fileOverride { "glob:**/build.sbt" { runner.dialect = scala213 @@ -14,4 +14,8 @@ fileOverride { "glob:**/project/**" { runner.dialect = scala213 } + + "glob:**/modules/app/**" { + runner.dialect = scala3 + } } diff --git a/build.sbt b/build.sbt index d51b5185..22e752f3 100644 --- a/build.sbt +++ b/build.sbt @@ -58,6 +58,7 @@ lazy val play = project lazy val api = (project in file("modules/api")) .enablePlugins(Smithy4sCodegenPlugin) .settings( + scalaVersion := scala3, name := "lila-search-api", libraryDependencies ++= Seq( "com.disneystreaming.smithy4s" %% "smithy4s-core" % smithy4sVersion.value @@ -69,6 +70,7 @@ lazy val app = (project in file("modules/app")) .settings( name := "lila-search", commonSettings, + scalaVersion := scala3, libraryDependencies ++= Seq( "com.disneystreaming.smithy4s" %% "smithy4s-http4s" % smithy4sVersion.value, "com.disneystreaming.smithy4s" %% "smithy4s-http4s-swagger" % smithy4sVersion.value, diff --git a/modules/app/src/main/scala/app.config.scala b/modules/app/src/main/scala/app.config.scala index 21f2f20b..4e834847 100644 --- a/modules/app/src/main/scala/app.config.scala +++ b/modules/app/src/main/scala/app.config.scala @@ -2,12 +2,12 @@ package lila.search package app import cats.effect.IO -import cats.syntax.all._ -import ciris._ -import ciris.http4s._ -import com.comcast.ip4s._ +import cats.syntax.all.* +import ciris.* +import ciris.http4s.* +import com.comcast.ip4s.* -object AppConfig { +object AppConfig: def load: IO[AppConfig] = appConfig.load[IO] @@ -16,8 +16,6 @@ object AppConfig { ElasticConfig.config ).parMapN(AppConfig.apply) -} - case class AppConfig( server: HttpServerConfig, elastic: ElasticConfig @@ -25,17 +23,15 @@ case class AppConfig( case class HttpServerConfig(host: Host, port: Port, shutdownTimeout: Int) -object HttpServerConfig { +object HttpServerConfig: private def host = env("HTTP_HOST").or(prop("http.host")).as[Host].default(ip"0.0.0.0") - private def port = env("HTTP_PORT").or(prop("http.port")).as[Port].default(port"9669") + private def port = env("HTTP_PORT").or(prop("http.port")).as[Port].default(port"9673") private def shutdownTimeout = env("HTTP_SHUTDOWN_TIMEOUT").or(prop("http.shutdown.timeout")).as[Int].default(30) def config = (host, port, shutdownTimeout).parMapN(HttpServerConfig.apply) -} case class ElasticConfig(uri: String) -object ElasticConfig { - private def uri = env("ELASTIC_URI").or(prop("elastic.uri")).as[String] +object ElasticConfig: + private def uri = env("ELASTIC_URI").or(prop("elastic.uri")).as[String].default("http://127.0.0.1:9200") def config = uri.map(ElasticConfig.apply) -} diff --git a/modules/app/src/main/scala/app.scala b/modules/app/src/main/scala/app.scala new file mode 100644 index 00000000..caaca7a9 --- /dev/null +++ b/modules/app/src/main/scala/app.scala @@ -0,0 +1,27 @@ +package lila.search +package app + +import cats.effect.* +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.slf4j.Slf4jLogger + +object App extends IOApp.Simple: + + given Logger[IO] = Slf4jLogger.getLogger[IO] + + override def run: IO[Unit] = app.useForever + + def app: Resource[IO, Unit] = + for + config <- AppConfig.load.toResource + _ <- Logger[IO].info(s"Starting fide-api with config: $config").toResource + _ <- SearchApp(config).run() + yield () + +class SearchApp(config: AppConfig)(using Logger[IO]): + def run(): Resource[IO, Unit] = + for + httpApp <- Routes + server <- MkHttpServer.apply.newEmber(config.server, httpApp) + _ <- Logger[IO].info(s"Starting server on ${config.server.host}:${config.server.port}").toResource + yield () diff --git a/modules/app/src/main/scala/http.middleware.scala b/modules/app/src/main/scala/http.middleware.scala new file mode 100644 index 00000000..18cdede7 --- /dev/null +++ b/modules/app/src/main/scala/http.middleware.scala @@ -0,0 +1,19 @@ +package lila.search +package app + +import cats.effect.IO +import org.http4s.* +import org.http4s.implicits.* +import org.http4s.server.middleware.* + +import scala.concurrent.duration.* + +type Middleware = HttpRoutes[IO] => HttpRoutes[IO] +def ApplyMiddleware(routes: HttpRoutes[IO]): HttpApp[IO] = + + val autoSlash: Middleware = AutoSlash(_) + val timeout: Middleware = Timeout(60.seconds) + + val middleware = autoSlash.andThen(timeout) + + middleware(routes).orNotFound diff --git a/modules/app/src/main/scala/http.routes.scala b/modules/app/src/main/scala/http.routes.scala new file mode 100644 index 00000000..5ab235ce --- /dev/null +++ b/modules/app/src/main/scala/http.routes.scala @@ -0,0 +1,31 @@ +package lila.search +package app + +import cats.data.NonEmptyList +import cats.effect.{ IO, Resource } +import cats.syntax.all.* +import lila.search.spec.* +import org.http4s.{ HttpApp, HttpRoutes } +import org.typelevel.log4cats.Logger +import smithy4s.http4s.SimpleRestJsonBuilder + +def Routes(using Logger[IO]): Resource[IO, HttpApp[IO]] = + + val healthServiceImpl: HealthService[IO] = new HealthService.Default[IO](IO.stub) + + val searchServiceImpl: SearchService[IO] = new SearchService.Default[IO](IO.stub) + + val search: Resource[IO, HttpRoutes[IO]] = + SimpleRestJsonBuilder.routes(searchServiceImpl).resource + + val health: Resource[IO, HttpRoutes[IO]] = + SimpleRestJsonBuilder.routes(healthServiceImpl).resource + + val docs = smithy4s.http4s.swagger.docs[IO](SearchService, HealthService) + + NonEmptyList + .of(search, health) + .sequence + .map(_.reduceK) + .map(_ <+> docs) + .map(ApplyMiddleware) diff --git a/modules/app/src/main/scala/http.server.scala b/modules/app/src/main/scala/http.server.scala new file mode 100644 index 00000000..4bb9f1c9 --- /dev/null +++ b/modules/app/src/main/scala/http.server.scala @@ -0,0 +1,32 @@ +package lila.search +package app + +import cats.effect.{ IO, Resource } +import fs2.io.net.Network +import org.http4s.* +import org.http4s.ember.server.EmberServerBuilder +import org.http4s.server.Server +import org.typelevel.log4cats.Logger + +import scala.concurrent.duration.* + +trait MkHttpServer: + def newEmber(cfg: HttpServerConfig, httpApp: HttpApp[IO]): Resource[IO, Server] + +object MkHttpServer: + + def apply(using server: MkHttpServer): MkHttpServer = server + + given forAsyncLogger(using Logger[IO]): MkHttpServer = new: + + def newEmber(cfg: HttpServerConfig, httpApp: HttpApp[IO]): Resource[IO, Server] = EmberServerBuilder + .default[IO] + .withHost(cfg.host) + .withPort(cfg.port) + .withHttpApp(httpApp) + .withShutdownTimeout(cfg.shutdownTimeout.seconds) + .build + .evalTap(showBanner) + + private def showBanner(s: Server): IO[Unit] = + Logger[IO].info(s"lila-search started at ${s.address}") diff --git a/modules/core/src/main/scala/forum.scala b/modules/core/src/main/scala/forum.scala index 94014e13..6b5ad51a 100644 --- a/modules/core/src/main/scala/forum.scala +++ b/modules/core/src/main/scala/forum.scala @@ -44,7 +44,7 @@ object ForumQuery { private def makeQuery(query: Forum) = boolQuery().must( parsed(query.text).terms.map { term => - multiMatchQuery(term).fields(searchableFields*) + multiMatchQuery(term).fields(searchableFields *) } ::: List( parsed(query.text)("user").map { termQuery(Fields.author, _) }, (!query.troll).option(termQuery(Fields.troll, false)) diff --git a/modules/core/src/main/scala/study.scala b/modules/core/src/main/scala/study.scala index 6e0d9b60..afe474b9 100644 --- a/modules/core/src/main/scala/study.scala +++ b/modules/core/src/main/scala/study.scala @@ -57,7 +57,7 @@ object StudyQuery { else multiMatchQuery( parsed(query.text).terms.mkString(" ") - ).fields(searchableFields*).analyzer("english").matchType("most_fields") + ).fields(searchableFields *).analyzer("english").matchType("most_fields") must { matcher :: List( parsed(query.text)("owner").map { termQuery(Fields.owner, _) }, diff --git a/modules/core/src/main/scala/team.scala b/modules/core/src/main/scala/team.scala index d28e788b..e17817fa 100644 --- a/modules/core/src/main/scala/team.scala +++ b/modules/core/src/main/scala/team.scala @@ -39,7 +39,7 @@ object TeamQuery { private def makeQuery(team: Team) = must { parsed(team).terms.map { term => - multiMatchQuery(term).fields(searchableFields*) + multiMatchQuery(term).fields(searchableFields *) } } }