Created
November 3, 2020 16:03
-
-
Save absolutejam/01940b84acb373928850ee9619778556 to your computer and use it in GitHub Desktop.
Saturn
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module RealmWeaver.Core.Config | |
open System.IO | |
open Microsoft.Extensions.Configuration | |
open RealmWeaver.Core.Utils | |
let buildConfig environment (configBuilder: IConfigurationBuilder) = | |
let settingsPath = | |
match EnvVar.tryGet (sprintf "%s_%s" EnvVar.Root "CONFIG_FILE") with | |
| Some value -> value |> Path.GetFileNameWithoutExtension | |
| None -> "settings" | |
let envSettingsPath = sprintf "%s.%s" settingsPath environment | |
configBuilder | |
.SetBasePath(Directory.GetCurrentDirectory ()) | |
.AddJsonFile(settingsPath + ".json", optional=true, reloadOnChange=true) | |
.AddJsonFile(envSettingsPath + ".json", optional=true, reloadOnChange=true) | |
.AddYamlFile(settingsPath + ".yaml", optional=false, reloadOnChange=true) | |
.AddYamlFile(envSettingsPath + ".yaml", optional=true, reloadOnChange=true) | |
.AddEnvironmentVariables(prefix=EnvVar.Root + "_") | |
.Build() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module RealmWeaver.Server.Configuration | |
open System | |
open System.IO | |
open Neo4j.Driver | |
open Serilog.Events | |
open RealmWeaver.Core | |
open Microsoft.Extensions.Configuration | |
open RealmWeaver.Core.Infrastructure | |
let logger = Logging.makeStartupLogger () | |
/// F#-friendly config. | |
module ParsedConfig = | |
exception ConfigError of string | |
type Neo4jConfig = | |
{ | |
Uri: string | |
Username: string option | |
Password: string option | |
} | |
type LoggingConfig = | |
{ | |
SentryDsn: string option | |
SeqUri: string option | |
MinimumLogLevel: LogEventLevel | |
} | |
type EventStoreConfig = | |
{ | |
CacheMb: int | |
InMemory: bool | |
Username: string option | |
Password: string option | |
ConnectionString: Uri | |
} | |
type WebServerConfig = | |
{ | |
Port: uint16 | |
PublicPath: string | |
} | |
type AppConfig = | |
{ | |
Neo4j: Neo4jConfig | |
Logging: LoggingConfig | |
WebServer: WebServerConfig | |
EventStore: EventStoreConfig | |
} | |
let makeNeo4jDriver (config: Neo4jConfig) = | |
let auth = | |
match config with | |
| { Username = None; Password = None } -> | |
AuthTokens.None | |
| { Username = None; Password = Some _ } -> | |
failwith "Username specified - Password is required" | |
| { Username = Some _; Password = None } -> | |
failwith "Password specified - Username is specified" | |
| { Username = Some username; Password = Some password } -> | |
AuthTokens.Basic (username, password) | |
GraphDatabase.Driver (config.Uri, auth) | |
let makeStoreConnection (config: EventStoreConfig) = | |
let storeConfig = | |
if config.InMemory then | |
Store.Config.MemoryStore | |
else | |
match config with | |
| { Username = None } | |
| { Password = None } -> | |
failwith "Username or Password not provided" | |
| { Username = Some username; Password = Some password } -> | |
Store.Config.EventStore ( | |
config.ConnectionString, | |
username, | |
password, | |
config.CacheMb | |
) | |
Store.connect storeConfig | |
/// Contains POCOs that are used by `IConfiguration` to read from configuration | |
/// sources. All values are C#-friendly primitives (strings, nulls, etc.) and | |
/// some defaults are provided. | |
module Raw = | |
open ParsedConfig | |
type IConfigSettings<'t> = | |
abstract ToConfig : unit -> 't | |
let toConfig (configSettings: IConfigSettings<_>) = configSettings.ToConfig () | |
type Neo4jSettings () = | |
member val Uri: string = "bolt://localhost:7687" with get, set | |
member val Username: string = null with get, set | |
member val Password: string = null with get, set | |
interface IConfigSettings<Neo4jConfig> with | |
member this.ToConfig () = | |
{ | |
Uri = this.Uri | |
Username = this.Username |> Option.ofObj | |
Password = this.Password |> Option.ofObj | |
} | |
let parseLogLevel logLevel = | |
match LogEventLevel.TryParse logLevel with | |
| true, value -> value | |
| false, _ -> | |
if not (logLevel |> isNull) | |
then logger.Error ("CONFIG: Could not parse log level {logLevel}. Defaulting to Warning.", logLevel) | |
LogEventLevel.Warning | |
type LoggingSettings () = | |
member val SeqUri: string = null with get, set | |
member val SentryDsn: string = null with get, set | |
member val MinimumLogLevel: string = null with get, set | |
interface IConfigSettings<LoggingConfig> with | |
member this.ToConfig () = | |
{ | |
SeqUri = this.SeqUri |> Option.ofObj | |
SentryDsn = this.SentryDsn |> Option.ofObj | |
MinimumLogLevel = parseLogLevel this.MinimumLogLevel | |
} | |
type EventStoreSettings () = | |
member val InMemory: bool = false with get, set | |
member val CacheMb: int = 10 with get, set | |
member val Username: string = null with get, set | |
member val Password: string = null with get, set | |
member val ConnectionString: string = "tcp://localhost:1113" with get, set | |
interface IConfigSettings<EventStoreConfig> with | |
member this.ToConfig () = | |
{ | |
CacheMb = this.CacheMb | |
InMemory = this.InMemory | |
Username = this.Username |> Option.ofObj | |
Password = this.Password |> Option.ofObj | |
ConnectionString = Uri this.ConnectionString | |
} | |
type WebServerSettings () = | |
member val Port: uint16 = 8085us with get, set | |
member val PublicPath: string = Path.GetFullPath "../Client/public" with get, set | |
interface IConfigSettings<WebServerConfig> with | |
member this.ToConfig () = | |
{ | |
Port = this.Port | |
PublicPath = this.PublicPath | |
} | |
open ParsedConfig | |
open Raw | |
let getExn<'t> key (configRoot: IConfigurationRoot) : 't = | |
match configRoot.GetSection(key).Get<'t>() |> box with | |
| null -> failwith (sprintf "Could not retrieve type '%s' from IConfigurationRoot" (typeof<'t>.Name)) | |
| x -> unbox x | |
let loadExn environment : AppConfig = | |
let configRoot = | |
ConfigurationBuilder () | |
|> Config.buildConfig environment | |
{ | |
Neo4j = configRoot |> getExn<Neo4jSettings> "Neo4j" |> toConfig | |
Logging = configRoot |> getExn<LoggingSettings> "Logging" |> toConfig | |
WebServer = configRoot |> getExn<WebServerSettings> "WebServer" |> toConfig | |
EventStore = configRoot |> getExn<EventStoreSettings> "EventStore" |> toConfig | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module RealmWeaver.Server.Program | |
open System | |
open Saturn | |
open RealmWeaver.Core | |
open RealmWeaver.Server | |
open RealmWeaver.Core.Utils | |
open RealmWeaver.Server.Web.CompositionRoot | |
open RealmWeaver.Server.Configuration.ParsedConfig | |
open RealmWeaver.Server.Web.Extensions.SaturnApplicationBuilder | |
let logger = Logging.makeStartupLogger () | |
let environment = | |
let defaultEnv = "Development" | |
match EnvVar.tryGet "DOTNET_ENVIRONMENT" with | |
| Some env -> env | |
| None -> | |
logger.Information ("No environment specified - Defaulting to {default}", defaultEnv) | |
defaultEnv | |
let buildApp (config: AppConfig) = application { | |
url (sprintf "http://0.0.0.0:%i/" config.WebServer.Port) | |
use_router completeApi | |
memory_cache | |
use_static config.WebServer.PublicPath | |
use_gzip | |
use_serilog (Logging.makeSerilogILogger ()) | |
use_sentry (fun sentry -> | |
if config.Logging.SentryDsn |> Option.isSome then sentry.Dsn <- config.Logging.SentryDsn.Value | |
sentry.AttachStacktrace <- true | |
sentry.ShutdownTimeout <- TimeSpan.FromSeconds 3. | |
sentry.MaxBreadcrumbs <- 200) | |
service_config (configureServices config) | |
} | |
type ExitCode = | |
| Ok = 0 | |
| Error = 1 | |
| ConfigFailure = 2 | |
[<EntryPoint>] | |
let main _args = | |
try | |
let appConfig = Configuration.loadExn environment | |
buildApp appConfig |> run | |
ExitCode.Ok | |
with | |
| ConfigError err -> | |
logger.Fatal ("Config error: {err}", err) | |
ExitCode.ConfigFailure | |
| e -> | |
logger.Fatal ("Fatal error: " + e.Message) | |
ExitCode.Error | |
|> int |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment