Index tweaks

also:
- created modules for converters and indexes
- ensure we use our JSON converters for both RavenDB and Giraffe
- first cut at a composable function pipeline instead of method chaining for IWebHostBuilder
This commit is contained in:
Daniel J. Summers 2019-07-28 22:21:05 -05:00
parent a2ef84bc1e
commit 87ce966ca1
2 changed files with 175 additions and 137 deletions

View File

@ -9,65 +9,79 @@ open Raven.Client.Documents.Linq
open System open System
open System.Collections.Generic open System.Collections.Generic
/// JSON converter for request IDs /// JSON converters for various DUs
type RequestIdJsonConverter () = module Converters =
/// JSON converter for request IDs
type RequestIdJsonConverter () =
inherit JsonConverter<RequestId> () inherit JsonConverter<RequestId> ()
override __.WriteJson(writer : JsonWriter, value : RequestId, _ : JsonSerializer) = override __.WriteJson(writer : JsonWriter, value : RequestId, _ : JsonSerializer) =
(RequestId.toString >> writer.WriteValue) value (RequestId.toString >> writer.WriteValue) value
override __.ReadJson(reader: JsonReader, _ : Type, _ : RequestId, _ : bool, _ : JsonSerializer) = override __.ReadJson(reader: JsonReader, _ : Type, _ : RequestId, _ : bool, _ : JsonSerializer) =
(string >> RequestId.fromIdString) reader.Value (string >> RequestId.fromIdString) reader.Value
/// JSON converter for user IDs
/// JSON converter for user IDs type UserIdJsonConverter () =
type UserIdJsonConverter () =
inherit JsonConverter<UserId> () inherit JsonConverter<UserId> ()
override __.WriteJson(writer : JsonWriter, value : UserId, _ : JsonSerializer) = override __.WriteJson(writer : JsonWriter, value : UserId, _ : JsonSerializer) =
(UserId.toString >> writer.WriteValue) value (UserId.toString >> writer.WriteValue) value
override __.ReadJson(reader: JsonReader, _ : Type, _ : UserId, _ : bool, _ : JsonSerializer) = override __.ReadJson(reader: JsonReader, _ : Type, _ : UserId, _ : bool, _ : JsonSerializer) =
(string >> UserId) reader.Value (string >> UserId) reader.Value
/// JSON converter for Ticks
/// JSON converter for Ticks type TicksJsonConverter () =
type TicksJsonConverter () =
inherit JsonConverter<Ticks> () inherit JsonConverter<Ticks> ()
override __.WriteJson(writer : JsonWriter, value : Ticks, _ : JsonSerializer) = override __.WriteJson(writer : JsonWriter, value : Ticks, _ : JsonSerializer) =
(Ticks.toLong >> writer.WriteValue) value (Ticks.toLong >> writer.WriteValue) value
override __.ReadJson(reader: JsonReader, _ : Type, _ : Ticks, _ : bool, _ : JsonSerializer) = override __.ReadJson(reader: JsonReader, _ : Type, _ : Ticks, _ : bool, _ : JsonSerializer) =
(string >> int64 >> Ticks) reader.Value (string >> int64 >> Ticks) reader.Value
/// Index requests by user ID /// A sequence of all custom converters for myPrayerJournal
type Requests_ByUserId () as this = let all : JsonConverter seq =
seq {
yield RequestIdJsonConverter ()
yield UserIdJsonConverter ()
yield TicksJsonConverter ()
}
/// RavenDB index declarations
module Indexes =
/// Index requests by user ID
type Requests_ByUserId () as this =
inherit AbstractJavaScriptIndexCreationTask () inherit AbstractJavaScriptIndexCreationTask ()
do do
this.Maps <- HashSet<string> [ "map('Requests', function (req) { return { userId : req.userId } })" ] this.Maps <- HashSet<string> [ "docs.Requests.Select(req => new { userId = req.userId })" ]
/// Index requests for a journal view /// Index requests for a journal view
type Requests_AsJournal () as this = type Requests_AsJournal () as this =
inherit AbstractJavaScriptIndexCreationTask () inherit AbstractJavaScriptIndexCreationTask ()
do do
this.Maps <- HashSet<string> [ this.Maps <- HashSet<string> [
"map('Requests', function (req) { "docs.Requests.Select(req => new {
var hist = req.history requestId = req.Id,
.filter(function (hist) { return hist.text !== null }) userId = req.userId,
.sort(function (a, b) { return b - a }) text = req.history.Where(hist => hist.text != null).OrderByDescending(hist => hist.asOf).First().text,
return { asOf = req.history.OrderByDescending(hist => hist.asOf).First().asOf,
requestId : req.Id, snoozedUntil = req.snoozedUntil,
userId : req.userId, showAfter = req.showAfter,
text : hist[0].text, recurType = req.recurType,
asOf : req.history[req.history.length - 1].asOf, recurCount = req.recurCount
snoozedUntil : req.snoozedUntil,
showAfter : req.showAfter,
recurType : req.recurType,
recurCount : req.recurCount
}
})" })"
] ]
this.Fields <-
[ "text", IndexFieldOptions (Storage = Nullable FieldStorage.Yes)
"asOf", IndexFieldOptions (Storage = Nullable FieldStorage.Yes)
]
|> dict
|> Dictionary<string, IndexFieldOptions>
/// Extensions on the IAsyncDocumentSession interface to support our data manipulation needs /// Extensions on the IAsyncDocumentSession interface to support our data manipulation needs
[<AutoOpen>] [<AutoOpen>]
module Extensions = module Extensions =
open Indexes
open Raven.Client.Documents.Commands.Batches open Raven.Client.Documents.Commands.Batches
open Raven.Client.Documents.Operations open Raven.Client.Documents.Operations
open Raven.Client.Documents.Session open Raven.Client.Documents.Session

View File

@ -2,50 +2,70 @@ namespace MyPrayerJournal.Api
open Microsoft.AspNetCore.Builder open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting open Microsoft.AspNetCore.Hosting
open System.IO
/// Configuration functions for the application /// Configuration functions for the application
module Configure = module Configure =
open Microsoft.Extensions.Configuration /// Configure the content root
open Newtonsoft.Json let contentRoot root (bldr : IWebHostBuilder) =
bldr.UseContentRoot root
/// Set up the configuration for the app open Microsoft.Extensions.Configuration
/// Configure the application configuration
let appConfiguration (bldr : IWebHostBuilder) =
let configuration (ctx : WebHostBuilderContext) (cfg : IConfigurationBuilder) = let configuration (ctx : WebHostBuilderContext) (cfg : IConfigurationBuilder) =
cfg.SetBasePath(ctx.HostingEnvironment.ContentRootPath) cfg.SetBasePath(ctx.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", optional = true, reloadOnChange = true) .AddJsonFile("appsettings.json", optional = true, reloadOnChange = true)
.AddJsonFile(sprintf "appsettings.%s.json" ctx.HostingEnvironment.EnvironmentName) .AddJsonFile(sprintf "appsettings.%s.json" ctx.HostingEnvironment.EnvironmentName)
.AddEnvironmentVariables () .AddEnvironmentVariables ()
|> ignore |> ignore
bldr.ConfigureAppConfiguration configuration
open Microsoft.AspNetCore.Server.Kestrel.Core open Microsoft.AspNetCore.Server.Kestrel.Core
/// Configure Kestrel from appsettings.json /// Configure Kestrel from appsettings.json
let kestrel (bldr : IWebHostBuilder) =
let kestrel (ctx : WebHostBuilderContext) (opts : KestrelServerOptions) = let kestrel (ctx : WebHostBuilderContext) (opts : KestrelServerOptions) =
(ctx.Configuration.GetSection >> opts.Configure >> ignore) "Kestrel" (ctx.Configuration.GetSection >> opts.Configure >> ignore) "Kestrel"
bldr.ConfigureKestrel kestrel
open Giraffe.Serialization /// Configure the web root directory
open Microsoft.FSharpLu.Json let webRoot pathSegments (bldr : IWebHostBuilder) =
(Path.Combine >> bldr.UseWebRoot) pathSegments
/// Custom settings for the JSON serializer (uses compact representation for options and DUs)
let jsonSettings =
let x = NewtonsoftJsonSerializer.DefaultSettings
x.Converters.Add (CompactUnionJsonConverter (true))
x.NullValueHandling <- NullValueHandling.Ignore
x.MissingMemberHandling <- MissingMemberHandling.Error
x.Formatting <- Formatting.Indented
x
open Giraffe open Giraffe
open Giraffe.Serialization
open Giraffe.TokenRouter open Giraffe.TokenRouter
open Microsoft.AspNetCore.Authentication.JwtBearer open Microsoft.AspNetCore.Authentication.JwtBearer
open Microsoft.Extensions.DependencyInjection open Microsoft.Extensions.DependencyInjection
open Microsoft.FSharpLu.Json
open MyPrayerJournal open MyPrayerJournal
open MyPrayerJournal.Indexes
open Newtonsoft.Json
open Raven.Client.Documents open Raven.Client.Documents
open Raven.Client.Documents.Indexes open Raven.Client.Documents.Indexes
open System.Security.Cryptography.X509Certificates open System.Security.Cryptography.X509Certificates
/// Configure dependency injection /// Configure dependency injection
let services (sc : IServiceCollection) = let services (bldr : IWebHostBuilder) =
let svcs (sc : IServiceCollection) =
/// A set of JSON converters used for both Giraffe's request serialization and RavenDB's storage
let jsonConverters : JsonConverter seq =
seq {
yield! Converters.all
yield CompactUnionJsonConverter true
}
/// Custom settings for the JSON serializer (uses compact representation for options and DUs)
let jsonSettings =
let x = NewtonsoftJsonSerializer.DefaultSettings
jsonConverters |> List.ofSeq |> List.iter x.Converters.Add
x.NullValueHandling <- NullValueHandling.Ignore
x.MissingMemberHandling <- MissingMemberHandling.Error
x.Formatting <- Formatting.Indented
x
use sp = sc.BuildServiceProvider () use sp = sc.BuildServiceProvider ()
let cfg = sp.GetRequiredService<IConfiguration> () let cfg = sp.GetRequiredService<IConfiguration> ()
sc.AddGiraffe() sc.AddGiraffe()
@ -65,17 +85,26 @@ module Configure =
|> ignore |> ignore
let config = sc.BuildServiceProvider().GetRequiredService<IConfiguration>().GetSection "RavenDB" let config = sc.BuildServiceProvider().GetRequiredService<IConfiguration>().GetSection "RavenDB"
let store = new DocumentStore () let store = new DocumentStore ()
store.Urls <- [| config.["URLs"] |] store.Urls <- [| config.["URL"] |]
store.Database <- config.["Database"] store.Database <- config.["Database"]
store.Certificate <- new X509Certificate2 (config.["Certificate"], config.["Password"]) // store.Certificate <- new X509Certificate2 (config.["Certificate"], config.["Password"])
store.Conventions.CustomizeJsonSerializer <- (fun x -> store.Conventions.CustomizeJsonSerializer <- fun x -> jsonConverters |> List.ofSeq |> List.iter x.Converters.Add
x.Converters.Add (RequestIdJsonConverter ()) store.Initialize () |> (sc.AddSingleton >> ignore)
x.Converters.Add (TicksJsonConverter ())
x.Converters.Add (UserIdJsonConverter ())
x.Converters.Add (CompactUnionJsonConverter true))
store.Initialize () |> sc.AddSingleton |> ignore
IndexCreation.CreateIndexes (typeof<Requests_ByUserId>.Assembly, store) IndexCreation.CreateIndexes (typeof<Requests_ByUserId>.Assembly, store)
bldr.ConfigureServices svcs
open Microsoft.Extensions.Logging
/// Configure logging
let logging (bldr : IWebHostBuilder) =
let logz (log : ILoggingBuilder) =
let env = log.Services.BuildServiceProvider().GetService<IHostingEnvironment> ()
match env.IsDevelopment () with
| true -> log
| false -> log.AddFilter(fun l -> l > LogLevel.Information)
|> function l -> l.AddConsole().AddDebug()
|> ignore
bldr.ConfigureLogging logz
/// Routes for the available URLs within myPrayerJournal /// Routes for the available URLs within myPrayerJournal
let webApp = let webApp =
@ -108,8 +137,13 @@ module Configure =
] ]
] ]
open System
/// Configure the web application /// Configure the web application
let application (app : IApplicationBuilder) = let application (bldr : IWebHostBuilder) =
let appConfig =
Action<IApplicationBuilder> (
fun (app : IApplicationBuilder) ->
let env = app.ApplicationServices.GetService<IHostingEnvironment> () let env = app.ApplicationServices.GetService<IHostingEnvironment> ()
match env.IsDevelopment () with match env.IsDevelopment () with
| true -> app.UseDeveloperExceptionPage () | true -> app.UseDeveloperExceptionPage ()
@ -119,39 +153,29 @@ module Configure =
a.UseAuthentication() a.UseAuthentication()
.UseStaticFiles() .UseStaticFiles()
.UseGiraffe webApp .UseGiraffe webApp
|> ignore |> ignore)
bldr.Configure appConfig
open Microsoft.Extensions.Logging /// Compose all the configurations into one
let webHost appRoot pathSegments =
/// Configure logging contentRoot appRoot
let logging (log : ILoggingBuilder) = >> appConfiguration
let env = log.Services.BuildServiceProvider().GetService<IHostingEnvironment> () >> kestrel
match env.IsDevelopment () with >> webRoot (Array.concat [ [| appRoot |]; pathSegments ])
| true -> log >> services
| false -> log.AddFilter(fun l -> l > LogLevel.Information) >> logging
|> function l -> l.AddConsole().AddDebug() >> application
|> ignore
/// Build the web host from the given configuration
let buildHost (bldr : IWebHostBuilder) = bldr.Build ()
module Program = module Program =
open System
open System.IO
let exitCode = 0 let exitCode = 0
let CreateWebHostBuilder _ =
let contentRoot = Directory.GetCurrentDirectory ()
WebHostBuilder()
.UseContentRoot(contentRoot)
.ConfigureAppConfiguration(Configure.configuration)
.UseKestrel(Configure.kestrel)
.UseWebRoot(Path.Combine (contentRoot, "wwwroot"))
.ConfigureServices(Configure.services)
.ConfigureLogging(Configure.logging)
.Configure(Action<IApplicationBuilder> Configure.application)
[<EntryPoint>] [<EntryPoint>]
let main args = let main _ =
CreateWebHostBuilder(args).Build().Run() let appRoot = Directory.GetCurrentDirectory ()
use host = WebHostBuilder () |> (Configure.webHost appRoot [| "wwwroot" |] >> Configure.buildHost)
host.Run ()
exitCode exitCode