From 7a941ae08e0b35fcebfd1c804e1bfa97977b6e9d Mon Sep 17 00:00:00 2001 From: Nihal Mirpuri Date: Sat, 17 Feb 2024 14:14:43 +0000 Subject: [PATCH] Proof of concept - add API to patch config --- build.sbt | 5 ++++ src/main/scala/Routes.scala | 28 ++++++++++++++++++++ src/main/scala/Server.scala | 16 +++++++++-- src/main/scala/configuration/MapReader.scala | 8 ++++++ 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/Routes.scala create mode 100644 src/main/scala/configuration/MapReader.scala diff --git a/build.sbt b/build.sbt index 071cdac..660d0da 100644 --- a/build.sbt +++ b/build.sbt @@ -14,6 +14,7 @@ val circeGenericExtrasVersion = "0.14.3" val circeVersion = "0.14.6" val fs2Version = "3.7.0" val http4sVersion = "0.23.23" +val ip4sVersion = "3.3.0" val logbackVersion = "1.4.11" val scalamockVersion = "5.2.0" val scalatestVersion = "3.2.17" @@ -26,10 +27,14 @@ libraryDependencies ++= Seq( "org.scala-lang" % "scala-library" % scalaVersion.value % "provided", "ch.qos.logback" % "logback-classic" % logbackVersion % Runtime, "org.slf4j" % "slf4j-api" % slf4jVersion, + "com.comcast" %% "ip4s-core" % ip4sVersion, "org.http4s" %% "http4s-ember-client" % http4sVersion, + "org.http4s" %% "http4s-ember-server" % http4sVersion, + "org.http4s" %% "http4s-dsl" % http4sVersion, "org.http4s" %% "http4s-circe" % http4sVersion, "org.http4s" %% "http4s-client" % http4sVersion, "org.http4s" %% "http4s-core" % http4sVersion, + "org.http4s" %% "http4s-server" % http4sVersion, "co.fs2" %% "fs2-core" % fs2Version, "co.fs2" %% "fs2-io" % fs2Version, "com.chuusai" %% "shapeless" % shapelessVersion, diff --git a/src/main/scala/Routes.scala b/src/main/scala/Routes.scala new file mode 100644 index 0000000..897e170 --- /dev/null +++ b/src/main/scala/Routes.scala @@ -0,0 +1,28 @@ +import cats.effect._ +import configuration.{Configuration, ConfigurationRedactor, ConfigurationUtils, MapAndFileAndSystemPropertyReader} +import http.HttpClient +import org.http4s._ +import org.http4s.circe.CirceEntityCodec.circeEntityDecoder +import org.http4s.dsl.io._ +import org.slf4j.LoggerFactory + +object Routes { + + private val logger = LoggerFactory.getLogger(getClass) + private def routes(configRef: Ref[IO, Configuration], client: HttpClient): HttpRoutes[IO] = HttpRoutes.of[IO] { + + case req @ PATCH -> Root / "config" => + logger.info(s"Received request: $req") + val result = for { + rawConfiguration <- req.as[Map[String, String]] + _ = logger.info(s"Parsed into $rawConfiguration") + newConfiguration <- ConfigurationUtils.create(new MapAndFileAndSystemPropertyReader(rawConfiguration), client) + _ = logger.info(s"New configuration: $newConfiguration") + _ <- configRef.set(newConfiguration) + } yield newConfiguration + + Ok(result.map(ConfigurationRedactor.redactToString)) + } + + def service(configRef: Ref[IO, Configuration], client: HttpClient): HttpRoutes[IO] = routes(configRef, client) +} diff --git a/src/main/scala/Server.scala b/src/main/scala/Server.scala index c517628..46bb4c8 100644 --- a/src/main/scala/Server.scala +++ b/src/main/scala/Server.scala @@ -1,8 +1,11 @@ import cats.effect._ -import cats.implicits.catsSyntaxTuple3Parallel -import configuration.{Configuration, ConfigurationUtils, FileAndSystemPropertyReader, SystemPropertyReader} +import cats.implicits.catsSyntaxTuple4Parallel +import com.comcast.ip4s.IpLiteralSyntax +import configuration.{Configuration, ConfigurationUtils, FileAndSystemPropertyReader} import http.HttpClient +import org.http4s.ember.server.EmberServerBuilder +import org.http4s.server.middleware.CORS import org.slf4j.LoggerFactory import java.nio.channels.ClosedChannelException @@ -25,6 +28,7 @@ object Server extends IOApp { initialConfig <- ConfigurationUtils.create(configReader, httpClient) configRef <- Ref.of[IO, Configuration](initialConfig) result <- ( + server(configRef, httpClient), pingTokenSync(configRef, httpClient), plexTokenSync(configRef, httpClient), plexTokenDeleteSync(configRef, httpClient) @@ -32,6 +36,14 @@ object Server extends IOApp { } yield result } + private def server(configRef: Ref[IO, Configuration], client: HttpClient) = EmberServerBuilder + .default[IO] + .withHost(ipv4"0.0.0.0") + .withPort(port"3434") + .withHttpApp(CORS(Routes.service(configRef, client).orNotFound)) + .build + .use(_ => IO.never) + private def fetchLatestConfig(configRef: Ref[IO, Configuration]): IO[Configuration] = configRef.get diff --git a/src/main/scala/configuration/MapReader.scala b/src/main/scala/configuration/MapReader.scala new file mode 100644 index 0000000..c6f9c79 --- /dev/null +++ b/src/main/scala/configuration/MapReader.scala @@ -0,0 +1,8 @@ +package configuration + +class MapAndFileAndSystemPropertyReader(map: Map[String, String]) extends ConfigurationReader { + override def getConfigOption(key: String): Option[String] = map.get(key) match { + case r@Some(_) => r + case None => FileAndSystemPropertyReader.getConfigOption(key) + } +}