API/app working nicely with RavenDB
time to v2 the app...
This commit is contained in:
@@ -1,17 +1,14 @@
|
||||
namespace MyPrayerJournal
|
||||
|
||||
open FSharp.Control.Tasks.V2.ContextInsensitive
|
||||
open Microsoft.FSharpLu
|
||||
open Newtonsoft.Json
|
||||
open Raven.Client.Documents
|
||||
open Raven.Client.Documents.Indexes
|
||||
open Raven.Client.Documents.Linq
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
|
||||
/// JSON converters for various DUs
|
||||
module Converters =
|
||||
|
||||
open Microsoft.FSharpLu.Json
|
||||
open Newtonsoft.Json
|
||||
|
||||
/// JSON converter for request IDs
|
||||
type RequestIdJsonConverter () =
|
||||
inherit JsonConverter<RequestId> ()
|
||||
@@ -36,30 +33,28 @@ module Converters =
|
||||
override __.ReadJson(reader: JsonReader, _ : Type, _ : Ticks, _ : bool, _ : JsonSerializer) =
|
||||
(string >> int64 >> Ticks) reader.Value
|
||||
|
||||
/// A sequence of all custom converters for myPrayerJournal
|
||||
/// A sequence of all custom converters needed for myPrayerJournal
|
||||
let all : JsonConverter seq =
|
||||
seq {
|
||||
yield RequestIdJsonConverter ()
|
||||
yield UserIdJsonConverter ()
|
||||
yield TicksJsonConverter ()
|
||||
yield CompactUnionJsonConverter true
|
||||
}
|
||||
|
||||
|
||||
/// RavenDB index declarations
|
||||
module Indexes =
|
||||
|
||||
/// Index requests by user ID
|
||||
type Requests_ByUserId () as this =
|
||||
inherit AbstractJavaScriptIndexCreationTask ()
|
||||
do
|
||||
this.Maps <- HashSet<string> [ "docs.Requests.Select(req => new { userId = req.userId })" ]
|
||||
open Raven.Client.Documents.Indexes
|
||||
|
||||
/// Index requests for a journal view
|
||||
type Requests_AsJournal () as this =
|
||||
inherit AbstractJavaScriptIndexCreationTask ()
|
||||
do
|
||||
this.Maps <- HashSet<string> [
|
||||
"docs.Requests.Select(req => new {
|
||||
requestId = req.Id,
|
||||
"""docs.Requests.Select(req => new {
|
||||
requestId = req.Id.Replace("Requests/", ""),
|
||||
userId = req.userId,
|
||||
text = req.history.Where(hist => hist.text != null).OrderByDescending(hist => hist.asOf).First().text,
|
||||
asOf = req.history.OrderByDescending(hist => hist.asOf).First().asOf,
|
||||
@@ -68,10 +63,11 @@ module Indexes =
|
||||
showAfter = req.showAfter,
|
||||
recurType = req.recurType,
|
||||
recurCount = req.recurCount
|
||||
})"
|
||||
})"""
|
||||
]
|
||||
this.Fields <-
|
||||
[ "text", IndexFieldOptions (Storage = Nullable FieldStorage.Yes)
|
||||
[ "requestId", IndexFieldOptions (Storage = Nullable FieldStorage.Yes)
|
||||
"text", IndexFieldOptions (Storage = Nullable FieldStorage.Yes)
|
||||
"asOf", IndexFieldOptions (Storage = Nullable FieldStorage.Yes)
|
||||
"lastStatus", IndexFieldOptions (Storage = Nullable FieldStorage.Yes)
|
||||
]
|
||||
@@ -79,39 +75,14 @@ module Indexes =
|
||||
|> Dictionary<string, IndexFieldOptions>
|
||||
|
||||
|
||||
/// Extensions on the IAsyncDocumentSession interface to support our data manipulation needs
|
||||
[<AutoOpen>]
|
||||
module Extensions =
|
||||
|
||||
open Indexes
|
||||
open Raven.Client.Documents.Commands.Batches
|
||||
open Raven.Client.Documents.Operations
|
||||
open Raven.Client.Documents.Session
|
||||
|
||||
/// Format an RQL query by a strongly-typed index
|
||||
let fromIndex (typ : Type) =
|
||||
typ.Name.Replace ("_", "/") |> sprintf "from index '%s'"
|
||||
|
||||
/// Utility method to create a patch request to push an item on the end of a list
|
||||
let listPush<'T> listName docId (item : 'T) =
|
||||
let r = PatchRequest()
|
||||
r.Script <- sprintf "this.%s.push(args.Item)" listName
|
||||
r.Values.["Item"] <- item
|
||||
PatchCommandData (docId, null, r, null)
|
||||
|
||||
/// Utility method to create a patch to update a single field
|
||||
// TODO: think we need to include quotes if it's a string
|
||||
let fieldUpdate<'T> fieldName docId (item : 'T) =
|
||||
let r = PatchRequest()
|
||||
r.Script <- sprintf "this.%s = args.Item" fieldName
|
||||
r.Values.["Item"] <- item
|
||||
PatchCommandData (docId, null, r, null)
|
||||
|
||||
|
||||
/// All data manipulations within myPrayerJournal
|
||||
module Data =
|
||||
|
||||
open FSharp.Control.Tasks.V2.ContextInsensitive
|
||||
open Indexes
|
||||
open Microsoft.FSharpLu
|
||||
open Raven.Client.Documents
|
||||
open Raven.Client.Documents.Linq
|
||||
open Raven.Client.Documents.Session
|
||||
|
||||
/// Add a history entry
|
||||
@@ -134,11 +105,15 @@ module Data =
|
||||
|
||||
/// Retrieve all answered requests for the given user
|
||||
let answeredRequests userId (sess : IAsyncDocumentSession) =
|
||||
sess.Query<JournalRequest, Requests_AsJournal>()
|
||||
.Where(fun r -> r.userId = userId && r.lastStatus = "Answered")
|
||||
.OrderByDescending(fun r -> r.asOf)
|
||||
.ProjectInto<JournalRequest>()
|
||||
.ToListAsync()
|
||||
task {
|
||||
let! reqs =
|
||||
sess.Query<JournalRequest, Requests_AsJournal>()
|
||||
.Where(fun r -> r.userId = userId && r.lastStatus = "Answered")
|
||||
.OrderByDescending(fun r -> r.asOf)
|
||||
.ProjectInto<JournalRequest>()
|
||||
.ToListAsync ()
|
||||
return List.ofSeq reqs
|
||||
}
|
||||
|
||||
/// Retrieve the user's current journal
|
||||
let journalByUserId userId (sess : IAsyncDocumentSession) =
|
||||
|
||||
@@ -43,21 +43,13 @@ type Recurrence =
|
||||
| Days
|
||||
| Weeks
|
||||
module Recurrence =
|
||||
/// The string reprsentation used in the database and the web app
|
||||
// TODO/FIXME: will this be true in v2? it's not in the database...
|
||||
let toString x =
|
||||
match x with
|
||||
| Immediate -> "immediate"
|
||||
| Hours -> "hours"
|
||||
| Days -> "days"
|
||||
| Weeks -> "weeks"
|
||||
/// Create a recurrence value from a string
|
||||
let fromString x =
|
||||
match x with
|
||||
| "immediate" -> Immediate
|
||||
| "hours" -> Hours
|
||||
| "days" -> Days
|
||||
| "weeks" -> Weeks
|
||||
| "Immediate" -> Immediate
|
||||
| "Hours" -> Hours
|
||||
| "Days" -> Days
|
||||
| "Weeks" -> Weeks
|
||||
| _ -> invalidOp (sprintf "%s is not a valid recurrence" x)
|
||||
/// The duration of the recurrence
|
||||
let duration x =
|
||||
@@ -159,8 +151,8 @@ with
|
||||
// RavenDB doesn't like the "@"-suffixed properties from record types in a ProjectInto clause
|
||||
[<NoComparison; NoEquality>]
|
||||
type JournalRequest () =
|
||||
/// The ID of the request
|
||||
[<DefaultValue>] val mutable requestId : RequestId
|
||||
/// The ID of the request (just the CUID part)
|
||||
[<DefaultValue>] val mutable requestId : string
|
||||
/// The ID of the user to whom the request belongs
|
||||
[<DefaultValue>] val mutable userId : UserId
|
||||
/// The current text of the request
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/// HTTP handlers for the myPrayerJournal API
|
||||
[<RequireQualifiedAccess>]
|
||||
module MyPrayerJournal.Api.Handlers
|
||||
module MyPrayerJournal.Handlers
|
||||
|
||||
open FSharp.Control.Tasks.V2.ContextInsensitive
|
||||
open Giraffe
|
||||
@@ -46,7 +46,9 @@ module private Helpers =
|
||||
|
||||
/// Create a RavenDB session
|
||||
let session (ctx : HttpContext) =
|
||||
ctx.GetService<IDocumentStore>().OpenAsyncSession ()
|
||||
let sess = ctx.GetService<IDocumentStore>().OpenAsyncSession ()
|
||||
sess.Advanced.WaitForIndexesAfterSaveChanges ()
|
||||
sess
|
||||
|
||||
/// Get the user's "sub" claim
|
||||
let user (ctx : HttpContext) =
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<Version>1.2.2.0</Version>
|
||||
<Version>2.0.0.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace MyPrayerJournal.Api
|
||||
module MyPrayerJournal.Api
|
||||
|
||||
open Microsoft.AspNetCore.Builder
|
||||
open Microsoft.AspNetCore.Hosting
|
||||
@@ -40,10 +40,10 @@ module Configure =
|
||||
open Giraffe.TokenRouter
|
||||
open Microsoft.AspNetCore.Authentication.JwtBearer
|
||||
open Microsoft.Extensions.DependencyInjection
|
||||
open Microsoft.FSharpLu.Json
|
||||
open MyPrayerJournal
|
||||
open MyPrayerJournal.Indexes
|
||||
open Newtonsoft.Json
|
||||
open Newtonsoft.Json.Serialization
|
||||
open Raven.Client.Documents
|
||||
open Raven.Client.Documents.Indexes
|
||||
open System.Security.Cryptography.X509Certificates
|
||||
@@ -51,19 +51,14 @@ module Configure =
|
||||
/// Configure dependency injection
|
||||
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
|
||||
Converters.all |> List.ofSeq |> List.iter x.Converters.Add
|
||||
x.NullValueHandling <- NullValueHandling.Ignore
|
||||
x.MissingMemberHandling <- MissingMemberHandling.Error
|
||||
x.Formatting <- Formatting.Indented
|
||||
x.ContractResolver <- DefaultContractResolver ()
|
||||
x
|
||||
|
||||
use sp = sc.BuildServiceProvider ()
|
||||
@@ -87,10 +82,12 @@ module Configure =
|
||||
let store = new DocumentStore ()
|
||||
store.Urls <- [| config.["URL"] |]
|
||||
store.Database <- config.["Database"]
|
||||
// store.Certificate <- new X509Certificate2 (config.["Certificate"], config.["Password"])
|
||||
store.Conventions.CustomizeJsonSerializer <- fun x -> jsonConverters |> List.ofSeq |> List.iter x.Converters.Add
|
||||
match isNull config.["Certificate"] with
|
||||
| true -> ()
|
||||
| false -> store.Certificate <- new X509Certificate2 (config.["Certificate"], config.["Password"])
|
||||
store.Conventions.CustomizeJsonSerializer <- fun x -> Converters.all |> List.ofSeq |> List.iter x.Converters.Add
|
||||
store.Initialize () |> (sc.AddSingleton >> ignore)
|
||||
IndexCreation.CreateIndexes (typeof<Requests_ByUserId>.Assembly, store)
|
||||
IndexCreation.CreateIndexes (typeof<Requests_AsJournal>.Assembly, store)
|
||||
bldr.ConfigureServices svcs
|
||||
|
||||
open Microsoft.Extensions.Logging
|
||||
@@ -169,13 +166,11 @@ module Configure =
|
||||
/// Build the web host from the given configuration
|
||||
let buildHost (bldr : IWebHostBuilder) = bldr.Build ()
|
||||
|
||||
module Program =
|
||||
|
||||
let exitCode = 0
|
||||
let exitCode = 0
|
||||
|
||||
[<EntryPoint>]
|
||||
let main _ =
|
||||
let appRoot = Directory.GetCurrentDirectory ()
|
||||
use host = WebHostBuilder() |> (Configure.webHost appRoot [| "wwwroot" |] >> Configure.buildHost)
|
||||
host.Run ()
|
||||
exitCode
|
||||
[<EntryPoint>]
|
||||
let main _ =
|
||||
let appRoot = Directory.GetCurrentDirectory ()
|
||||
use host = WebHostBuilder() |> (Configure.webHost appRoot [| "wwwroot" |] >> Configure.buildHost)
|
||||
host.Run ()
|
||||
exitCode
|
||||
|
||||
Reference in New Issue
Block a user