Skip to content

Commit

Permalink
Added ability for server to send config to ui
Browse files Browse the repository at this point in the history
  • Loading branch information
Kalin-Rudnicki committed Jan 29, 2024
1 parent 0139a01 commit 431dae3
Show file tree
Hide file tree
Showing 24 changed files with 218 additions and 107 deletions.
3 changes: 3 additions & 0 deletions harness-archive/api/src/main/resources/application.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,8 @@
"service": {
"sender": "[email protected]"
}
},
"ui": {
"logTolerance": "DETAILED"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import harness.sql.query.Transaction
import harness.web.*
import harness.zio.*
import zio.*
import zio.json.*

object ServerMain {

type StorageEnv = Transaction & SessionStorage & UserStorage & AppStorage & LogStorage & TraceStorage
type ServerEnv = JDBCConnectionPool & EmailService & StaleDataCleanser
type ServerEnv = JDBCConnectionPool & EmailService & StaleDataCleanser & UiConfig
type ReqEnv = StorageEnv

private final case class Config(
Expand All @@ -41,6 +42,13 @@ object ServerMain {

}

final case class UiConfig(
logTolerance: Logger.LogLevel,
)
object UiConfig {
implicit val jsonCodec: JsonCodec[UiConfig] = DeriveJsonCodec.gen
}

// This layer will be evaluated once when the server starts
val serverLayer: SHRLayer[Scope, ServerEnv] =
ZLayer.makeSome[HarnessEnv & Scope, ServerEnv](
Expand All @@ -51,6 +59,7 @@ object ServerMain {
HConfig.readLayer[EmailService.Config]("email", "service"),
EmailService.liveLayer,
StaleDataCleanser.live(1.minute, 1.minute, 1.minute, 5.minutes, 15.minutes, 1.hour, 1.hour, 6.hours),
HConfig.readLayer[UiConfig]("ui"),
)

val storageLayer: URLayer[JDBCConnection, StorageEnv] =
Expand All @@ -65,13 +74,15 @@ object ServerMain {
val reqLayer: SHRLayer[ServerEnv & JDBCConnection & Scope, ReqEnv] =
storageLayer

val routes: URIO[ServerConfig, Route[ServerEnv & ReqEnv]] =
Route.stdRoot(
R.User.routes,
R.App.routes,
R.Log.routes,
R.Telemetry.routes,
)
val routes: URIO[RunMode & UiConfig & ServerConfig, Route[ServerEnv & ReqEnv]] =
for {
runMode <- ZIO.service[RunMode]
uiConfig <- ZIO.service[UiConfig]
config = StdClientConfig(runMode, uiConfig.logTolerance).basic
r <- Route.stdRoot(config)(
R.User.routes,
)
} yield r

private val migrations: PlannedMigrations =
PlannedMigrations(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ abstract class CustomRouteSpec extends RouteSpec[ServerMain.ServerEnv, ServerMai

override final val serverLayer: SHRLayer[Scope, ServerMain.ServerEnv] = ServerMain.serverLayer
override final val reqLayer: SHRLayer[ServerMain.ServerEnv & JDBCConnection & Scope, ServerMain.ReqEnv] = ServerMain.reqLayer
override final val route: URIO[ServerConfig, Route[ServerMain.ServerEnv & ServerMain.ReqEnv]] = ServerMain.routes
override final val route: URIO[HarnessEnv & ServerEnv & ServerConfig, Route[ServerMain.ServerEnv & ServerMain.ReqEnv]] = ServerMain.routes

override def aspects: Chunk[TestAspectAtLeastR[Environment]] =
super.aspects ++ Chunk(TestAspect.samples(15))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package harness.archive.ui.web

import harness.core.RunMode
import harness.web.HasStdClientConfig
import harness.webUI.*
import harness.webUI.style.*
import harness.zio.Logger

object Main extends PageApp {

override protected val runMode: RunMode = RunMode.Prod

override protected val logTolerance: Logger.LogLevel = Logger.LogLevel.Debug
object Main extends PageApp[HasStdClientConfig.Basic] {

override val styleSheets: List[StyleSheet] = List(DefaultStyleSheet)

Expand Down
5 changes: 4 additions & 1 deletion harness-core/shared/src/main/scala/harness/core/Enum.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ object Enum {
implicit final val hasCompanion: HasCompanion[E] = Enum.HasCompanion(this)

protected val defaultToString: E => String = _.toString

def values: Array[E]

final lazy val enumValues: Seq[E] = values.toSeq
Expand All @@ -27,6 +27,9 @@ object Enum {

implicit object ToString extends EnumMap[String](defaultToString)

implicit val stringEncoder: StringEncoder[E] = ToString.encode(_)
implicit val stringDecoder: StringDecoder[E] = StringDecoder.fromOptionF(getClass.getSimpleName, ToString.decode)

}

// =====| |=====
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
package harness.core

enum RunMode { case Prod, Dev }
enum RunMode extends Enum[RunMode] { case Prod, Dev }
object RunMode extends Enum.Companion[RunMode]
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import zio.test.*
abstract class RouteSpec[
SE <: JDBCConnectionPool: EnvironmentTag,
RE <: Transaction: EnvironmentTag,
] extends ZIOSpec[HarnessEnv & Route[SE & RE] & TestEnvironment] {
] extends ZIOSpec[HarnessEnv & TestEnvironment] {

// =====| Types |=====

Expand All @@ -22,7 +22,7 @@ abstract class RouteSpec[
final type ProvidedEnv = Route[ServerEnv & ReqEnv] & CookieStorage
final type HttpEnv = HarnessEnv & ProvidedEnv & ServerEnv & JDBCConnection

final type TestSpec = Spec[Environment & ServerEnv & JDBCConnection & CookieStorage & Scope, Any]
final type TestSpec = Spec[Environment & ServerEnv & Route[ServerEnv & ReqEnv] & JDBCConnection & CookieStorage & Scope, Any]

// =====| Abstract |=====

Expand All @@ -32,7 +32,7 @@ abstract class RouteSpec[
// This layer will be evaluated for each http call
val reqLayer: SHRLayer[ServerEnv & JDBCConnection & Scope, ReqEnv]

val route: URIO[ServerConfig, Route[ServerEnv & ReqEnv]]
val route: URIO[HarnessEnv & ServerEnv & ServerConfig, Route[ServerEnv & ReqEnv]]

def routeSpec: TestSpec

Expand All @@ -51,7 +51,7 @@ abstract class RouteSpec[
request <- CookieStorage.applyCookies(request)
response <- ZIO.scoped(
route(request.method, request.path)
.provideSomeLayer[HttpEnv & Scope](
.provideSomeLayer[HttpEnv & HarnessEnv & Scope](
reqLayer ++ Transaction.savepointLayer ++ ZLayer.succeed(request),
),
)
Expand Down Expand Up @@ -91,22 +91,20 @@ abstract class RouteSpec[

override def bootstrap: HTaskLayer[Environment] =
Scope.default >+> (
harnessLayer ++
ZLayer.fromZIO(evalRoute) ++
testEnvironment
harnessLayer ++ testEnvironment
)

override final def spec: Spec[Environment & Scope, Any] =
(routeSpec @@ runInTransaction)
.provideSomeLayer[Environment & ServerEnv & Scope] {
.provideSomeLayer[Environment & ServerEnv & Route[ServerEnv & ReqEnv] & Scope] {
CookieStorage.emptyLayer ++ JDBCConnection.poolLayer
}
.provideSomeLayerShared[Environment & Scope] { serverLayer }
.provideSomeLayerShared[Environment & Scope] { serverLayer >+> ZLayer.fromZIO(evalRoute) }

// =====| Helpers |=====

private final lazy val evalRoute: UIO[Route[ServerEnv & ReqEnv]] =
route.provide(ZLayer.succeed(ServerConfig(None, "res", true, None)))
private final lazy val evalRoute: URIO[HarnessEnv & ServerEnv, Route[ServerEnv & ReqEnv]] =
route.provideSomeLayer[HarnessEnv & ServerEnv](ZLayer.succeed(ServerConfig(None, "res", true, None)))

final val harnessLayer: HTaskLayer[HarnessEnv] =
HarnessEnv.defaultLayer(logLevel)
Expand Down
16 changes: 12 additions & 4 deletions harness-http-server/src/main/scala/harness/http/server/Route.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package harness.http.server

import cats.syntax.option.*
import harness.core.*
import harness.web.*
import harness.zio.*
import zio.*
import zio.json.*
import zio.json.ast.Json

trait Route[-R] { self =>

Expand Down Expand Up @@ -43,15 +46,18 @@ object Route {
rec(routes.toList)
}

private val pageHtmlResponse: HttpResponse =
private def pageHtmlResponse(harnessUiConfig: HConfig): HttpResponse =
HttpResponse(
"""<!DOCTYPE html>
s"""<!DOCTYPE html>
|<html lang="en">
|
|<head>
| <meta charset="UTF-8">
| <title>Title</title>
| <link rel='shortcut icon' type='image/x-icon' href='/res/favicon.ico' />
| <script>
| const harnessUiConfig = ${Json.Str(harnessUiConfig.configJson.toJson).toJson}
| </script>
| <script id="scripts" src="/res/js/main.js"></script>
|</head>
|
Expand Down Expand Up @@ -81,8 +87,10 @@ object Route {
* - /res/js/{*} -> {res}/js/{*}
* - /res/css/{*} -> {res}/css/{*}
*/
def stdRoot[R](apis: Route[R]*): URIO[ServerConfig, Route[R]] =
def stdRoot[A <: HasStdClientConfig: JsonEncoder, R](harnessUiConfig: A)(apis: Route[R]*): URIO[ServerConfig, Route[R]] =
ZIO.serviceWith[ServerConfig] { config =>
val pageHtml = pageHtmlResponse(HConfig.unsafeFromEncodable(harnessUiConfig))

Route.oneOf(
"api" /: Route.oneOf(apis*),
(HttpMethod.GET / "api" / "health-check").implement { _ =>
Expand All @@ -94,7 +102,7 @@ object Route {
} yield HttpResponse.fromHttpCode.Ok
},
(HttpMethod.GET / "page" / RouteMatcher.**).implement { _ =>
ZIO.succeed(pageHtmlResponse)
ZIO.succeed(pageHtml)
},
HttpMethod.GET /: "res" /: Route.oneOf(
"favicon.ico".implement { _ =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package harness.payments

import cats.syntax.option.*
import harness.core.*
import harness.http.client.HttpClient
import harness.payments.facades.*
Expand All @@ -19,8 +20,12 @@ object PaymentsUI {
private val loader: Promise[Nothing, Unit] =
Unsafe.unsafely { Runtime.default.unsafe.run { Promise.make[Nothing, Unit] }.getOrThrow() }

val addStripeSrc: HRIO[Logger, Unit] =
private val stripeApiKey: Ref[Option[String]] =
Unsafe.unsafely { Runtime.default.unsafe.run { Ref.make(Option.empty[String]) }.getOrThrow() }

def initStripe(apiKey: String): HRIO[Logger, Unit] =
Logger.log.debug("Loading stripe src") *>
stripeApiKey.set(apiKey.some) *>
ZIO
.hAttempt {
val elem = document.createElement("script")
Expand Down Expand Up @@ -68,14 +73,14 @@ object PaymentsUI {
}
object PaymentEnv {

def create(apiKey: String): UIO[PaymentEnv] =
Ref.make[Elements](null).map { new PaymentEnv(Stripe(apiKey), _) }
val create: HTask[PaymentEnv] =
for {
apiKey <- stripeApiKey.get.someOrFail(HError.InternalDefect("API key was not initialized (PaymentsUI.initStripe)"))
elements <- Ref.make[Elements](null)
} yield new PaymentEnv(Stripe(apiKey), elements)

}

private val getUrlRegex: Regex =
s"^(https?://[^/]+)".r

def paymentForm(
createIntent: HRIO[HttpClient.ClientT & Logger & Telemetry, ClientSecret],
paymentsEnv: PaymentEnv,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ object PaymentProcessor {
}

private val initStripeMetadata: HTask[Unit] =
ZIO.hAttempt { Stripe.apiKey = config.apiKey }
ZIO.hAttempt { Stripe.apiKey = config.secretKey }

private def safeWrapStripeParams[A](thunk: => A)(implicit ct: ClassTag[A]): HTask[A] =
ZIO.hAttempt { thunk }.mapError(HError.SystemFailure(s"Error creating stripe params: ${ct.runtimeClass.getName}", _))
Expand Down Expand Up @@ -134,7 +134,10 @@ object PaymentProcessor {
val layer: URLayer[StripePaymentProcessor.Config, PaymentProcessor.StripePaymentProcessor] =
ZLayer.fromFunction { StripePaymentProcessor.apply }

final case class Config(apiKey: String)
final case class Config(
publishableKey: String,
secretKey: String,
)
object Config {
implicit val jsonCodec: JsonCodec[Config] = DeriveJsonCodec.gen
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ object TestMain extends ExecutableApp {
override val executable: Executable =
Executable
.withLayer {
ZLayer.succeed(PaymentProcessor.StripePaymentProcessor.Config("sk_test_51OVNiaD9gOL4yaWVWoQKP06HKSQ0kvbzT8AT4Bc9CmPx77OqA0jwwBjea0VxLIvt5UNLZSNiT8qlATdVhAr9URzm00nLuElEbN")) >>>
ZLayer.succeed(
PaymentProcessor.StripePaymentProcessor.Config(
"pk_test_51OVNiaD9gOL4yaWVMKdhPEOW59IeVbst1031HrqDDQRswYNFYAQtiOg9UDSyST7DYLGq8CYVN0bG0q51GovrVpVz0070Gb4ccu",
"sk_test_51OVNiaD9gOL4yaWVWoQKP06HKSQ0kvbzT8AT4Bc9CmPx77OqA0jwwBjea0VxLIvt5UNLZSNiT8qlATdVhAr9URzm00nLuElEbN",
),
) >>>
PaymentProcessor.StripePaymentProcessor.layer
}
.withEffect {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,8 @@
"service": {
"sender": "[email protected]"
}
},
"ui": {
"logTolerance": "DETAILED"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,25 @@ import harness.zio.*
import template.api.routes as R
import template.api.service.email.*
import template.api.service.storage.*
import template.model as D
import zio.*
import zio.json.*

object Main extends ExecutableApp {

override val config: ExecutableApp.Config = ExecutableApp.Config.default.addArchiveDecoders

type StorageEnv = Transaction & SessionStorage & UserStorage & PaymentMethodStorage
type ServerEnv = JDBCConnectionPool & EmailService & PaymentProcessor
type ServerEnv = JDBCConnectionPool & EmailService & PaymentProcessor & UiConfig & PaymentProcessor.StripePaymentProcessor.Config
type ReqEnv = StorageEnv

final case class UiConfig(
logTolerance: Logger.LogLevel,
)
object UiConfig {
implicit val jsonCodec: JsonCodec[UiConfig] = DeriveJsonCodec.gen
}

// This layer will be evaluated once when the server starts
val serverLayer: SHRLayer[Scope, ServerEnv] =
ZLayer.makeSome[HarnessEnv & Scope, ServerEnv](
Expand All @@ -36,6 +45,7 @@ object Main extends ExecutableApp {
EmailService.liveLayer,
HConfig.readLayer[PaymentProcessor.StripePaymentProcessor.Config]("payment", "stripe"),
PaymentProcessor.StripePaymentProcessor.layer,
HConfig.readLayer[UiConfig]("ui"),
)

val storageLayer: URLayer[JDBCConnection, StorageEnv] =
Expand All @@ -48,11 +58,20 @@ object Main extends ExecutableApp {
val reqLayer: SHRLayer[ServerEnv & JDBCConnection & Scope, ReqEnv] =
storageLayer

val routes: URIO[ServerConfig, Route[ServerEnv & ReqEnv]] =
Route.stdRoot(
R.User.routes,
R.Payment.routes,
)
val routes: URIO[RunMode & UiConfig & PaymentProcessor.StripePaymentProcessor.Config & ServerConfig, Route[ServerEnv & ReqEnv]] =
for {
runMode <- ZIO.service[RunMode]
uiConfig <- ZIO.service[UiConfig]
stripeConfig <- ZIO.service[PaymentProcessor.StripePaymentProcessor.Config]
config = D.config.UiConfig(
StdClientConfig(runMode, uiConfig.logTolerance),
stripeConfig.publishableKey,
)
r <- Route.stdRoot(config)(
R.User.routes,
R.Payment.routes,
)
} yield r

private val migrations: PlannedMigrations =
PlannedMigrations(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ abstract class CustomRouteSpec extends RouteSpec[Main.ServerEnv, Main.ReqEnv] {

override final val serverLayer: SHRLayer[Scope, Main.ServerEnv] = Main.serverLayer
override final val reqLayer: SHRLayer[Main.ServerEnv & JDBCConnection & Scope, Main.ReqEnv] = Main.reqLayer
override final val route: URIO[ServerConfig, Route[Main.ServerEnv & Main.ReqEnv]] = Main.routes
override final val route: URIO[HarnessEnv & Main.ServerEnv & ServerConfig, Route[Main.ServerEnv & Main.ReqEnv]] = Main.routes

override def aspects: Chunk[TestAspectAtLeastR[Environment]] =
super.aspects ++ Chunk(TestAspect.samples(15))
Expand Down
Loading

0 comments on commit 431dae3

Please sign in to comment.