diff --git a/build.sbt b/build.sbt index 0963b5d..22958aa 100644 --- a/build.sbt +++ b/build.sbt @@ -31,7 +31,7 @@ val Deps = new { val Gs = new { val Uuid: ModuleID = "gs" %% "gs-uuid-v0" % "0.4.2" - val Std: ModuleID = "gs" %% "gs-std-core-v0" % "0.1.2" + val Std: ModuleID = "gs" %% "gs-std-core-v0" % "0.1.3" val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.4.1" } @@ -44,6 +44,13 @@ val Deps = new { val EmberServer: ModuleID = "org.http4s" %% "http4s-ember-server" % Version } + val Circe = new { + private val Version: String = "0.14.15" + + val Generic: ModuleID = "io.circe" %% "circe-generic" % Version + val Literal: ModuleID = "io.circe" %% "circe-literal" % Version + } + val Log4Cats = new { val Slf4j: ModuleID = "org.typelevel" %% "log4cats-slf4j" % "2.8.0" } @@ -105,6 +112,8 @@ lazy val api = project Deps.Http4s.Dsl, Deps.Http4s.Circe, Deps.Http4s.EmberServer, + Deps.Circe.Generic, + Deps.Circe.Literal, Deps.Log4Cats.Slf4j, Deps.LogbackClassic ) diff --git a/modules/api/src/main/scala/gs/respite/api/RespiteApi.scala b/modules/api/src/main/scala/gs/respite/api/RespiteApi.scala index 053ba7a..af7c3a3 100644 --- a/modules/api/src/main/scala/gs/respite/api/RespiteApi.scala +++ b/modules/api/src/main/scala/gs/respite/api/RespiteApi.scala @@ -4,31 +4,24 @@ import cats.effect.ExitCode import cats.effect.IO import cats.effect.IOApp import com.comcast.ip4s._ -import org.http4s.HttpRoutes -import org.http4s.Method -import org.http4s.dsl.io.* +import gs.respite.db.MemoryKeySpaceDb import org.http4s.ember.server.EmberServerBuilder import org.typelevel.log4cats.LoggerFactory import org.typelevel.log4cats.slf4j.Slf4jFactory object RespiteApi extends IOApp: - given CanEqual[Method, Method] = CanEqual.derived - given CanEqual[org.http4s.Uri.Path, org.http4s.Uri.Path] = CanEqual.derived implicit val loggerFactory: LoggerFactory[IO] = Slf4jFactory.create[IO] - private val RespiteService = HttpRoutes - .of[IO] { case GET -> Root => - Ok("Hello, Respite!") - } - .orNotFound - override def run(args: List[String]): cats.effect.IO[ExitCode] = - EmberServerBuilder - .default[IO] - .withHost(ipv4"0.0.0.0") - .withPort(port"8080") - .withHttpApp(RespiteService) - .build - .use(_ => IO.never) - .as(ExitCode.Success) + for + db <- MemoryKeySpaceDb.initialize[IO] + exitCode <- EmberServerBuilder + .default[IO] + .withHost(ipv4"0.0.0.0") + .withPort(port"8080") + .withHttpApp(new RespiteRoutes[IO](db).app) + .build + .use(_ => IO.never) + .as(ExitCode.Success) + yield exitCode diff --git a/modules/api/src/main/scala/gs/respite/api/RespiteRoutes.scala b/modules/api/src/main/scala/gs/respite/api/RespiteRoutes.scala new file mode 100644 index 0000000..1fb49d2 --- /dev/null +++ b/modules/api/src/main/scala/gs/respite/api/RespiteRoutes.scala @@ -0,0 +1,40 @@ +package gs.respite.api + +import cats.effect.Async +import cats.syntax.all.* +import gs.respite.api.json.given +import gs.respite.db.KeySpaceDb +import gs.respite.model.Key +import gs.respite.model.Value +import org.http4s.EntityEncoder +import org.http4s.HttpApp +import org.http4s.HttpRoutes +import org.http4s.Method +import org.http4s.circe.* +import org.http4s.dsl.Http4sDsl + +final class RespiteRoutes[F[_]: Async]( + val db: KeySpaceDb[F] +) extends Http4sDsl[F]: + import RespiteRoutes.given + + given EntityEncoder[F, Value] = jsonEncoderOf[Value] + + val app: HttpApp[F] = HttpRoutes + .of[F] { case GET -> Root / "default" / key => + for + result <- db.default.get(Key.string(key)) + response <- + result match + case Some(value) => Ok(value) + case _ => NotFound() + yield response + } + .orNotFound + +object RespiteRoutes: + + given CanEqual[Method, Method] = CanEqual.derived + given CanEqual[org.http4s.Uri.Path, org.http4s.Uri.Path] = CanEqual.derived + +end RespiteRoutes diff --git a/modules/api/src/main/scala/gs/respite/api/json/encoders.scala b/modules/api/src/main/scala/gs/respite/api/json/encoders.scala new file mode 100644 index 0000000..638024a --- /dev/null +++ b/modules/api/src/main/scala/gs/respite/api/json/encoders.scala @@ -0,0 +1,28 @@ +package gs.respite.api.json + +import gs.respite.model.BooleanValue +import gs.respite.model.ByteValue +import gs.respite.model.DateValue +import gs.respite.model.Int64Value +import gs.respite.model.StringValue +import gs.respite.model.UUIDValue +import gs.respite.model.Value +import gs.std.v0.core.Base64Encoder +import io.circe.Encoder +import io.circe.literal.* + +given Encoder[Value] = Encoder.instance { (value: Value) => + value match + case v: StringValue => + json"""{"value": ${v.value}}""" + case v: BooleanValue => + json"""{"value": ${v.value}}""" + case v: Int64Value => + json"""{"value": ${v.value}}""" + case v: UUIDValue => + json"""{"value": ${v.value.withDashes()}}""" + case v: DateValue => + json"""{"value": ${v.value}}""" + case v: ByteValue => + json"""{"value": ${Base64Encoder.encode(v.value).data}}""" +}