Files
myPrayerJournal/src/MyPrayerJournal.Api/Data.fs
T

210 lines
7.9 KiB
FSharp
Raw Normal View History

2019-03-30 21:08:02 -05:00
namespace MyPrayerJournal
open FSharp.Control.Tasks.V2.ContextInsensitive
open Microsoft.FSharpLu
2019-07-13 22:55:53 -05:00
open Newtonsoft.Json
2019-07-27 20:02:01 -05:00
open Raven.Client.Documents
2019-07-13 22:55:53 -05:00
open Raven.Client.Documents.Indexes
2019-07-27 20:02:01 -05:00
open Raven.Client.Documents.Linq
2019-07-13 22:55:53 -05:00
open System
open System.Collections.Generic
2019-07-28 22:21:05 -05:00
/// JSON converters for various DUs
module Converters =
/// JSON converter for request IDs
type RequestIdJsonConverter () =
inherit JsonConverter<RequestId> ()
override __.WriteJson(writer : JsonWriter, value : RequestId, _ : JsonSerializer) =
(RequestId.toString >> writer.WriteValue) value
override __.ReadJson(reader: JsonReader, _ : Type, _ : RequestId, _ : bool, _ : JsonSerializer) =
(string >> RequestId.fromIdString) reader.Value
2019-07-13 22:55:53 -05:00
2019-07-28 22:21:05 -05:00
/// JSON converter for user IDs
type UserIdJsonConverter () =
inherit JsonConverter<UserId> ()
override __.WriteJson(writer : JsonWriter, value : UserId, _ : JsonSerializer) =
(UserId.toString >> writer.WriteValue) value
override __.ReadJson(reader: JsonReader, _ : Type, _ : UserId, _ : bool, _ : JsonSerializer) =
(string >> UserId) reader.Value
2019-07-13 22:55:53 -05:00
2019-07-28 22:21:05 -05:00
/// JSON converter for Ticks
type TicksJsonConverter () =
inherit JsonConverter<Ticks> ()
override __.WriteJson(writer : JsonWriter, value : Ticks, _ : JsonSerializer) =
(Ticks.toLong >> writer.WriteValue) value
override __.ReadJson(reader: JsonReader, _ : Type, _ : Ticks, _ : bool, _ : JsonSerializer) =
(string >> int64 >> Ticks) reader.Value
2019-07-13 22:55:53 -05:00
2019-07-28 22:21:05 -05:00
/// A sequence of all custom converters for myPrayerJournal
let all : JsonConverter seq =
seq {
yield RequestIdJsonConverter ()
yield UserIdJsonConverter ()
yield TicksJsonConverter ()
}
2019-07-13 22:55:53 -05:00
2019-07-28 22:21:05 -05:00
/// 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 })" ]
2019-07-13 22:55:53 -05:00
2019-07-28 22:21:05 -05:00
/// 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,
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,
2019-07-30 23:56:34 -05:00
lastStatus = req.history.OrderByDescending(hist => hist.asOf).First().status,
2019-07-28 22:21:05 -05:00
snoozedUntil = req.snoozedUntil,
showAfter = req.showAfter,
recurType = req.recurType,
recurCount = req.recurCount
})"
]
this.Fields <-
2019-07-30 23:56:34 -05:00
[ "text", IndexFieldOptions (Storage = Nullable FieldStorage.Yes)
"asOf", IndexFieldOptions (Storage = Nullable FieldStorage.Yes)
"lastStatus", IndexFieldOptions (Storage = Nullable FieldStorage.Yes)
2019-07-28 22:21:05 -05:00
]
|> dict
|> Dictionary<string, IndexFieldOptions>
2019-07-13 22:55:53 -05:00
/// Extensions on the IAsyncDocumentSession interface to support our data manipulation needs
[<AutoOpen>]
module Extensions =
2019-07-28 22:21:05 -05:00
open Indexes
2019-07-13 22:55:53 -05:00
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'"
2019-07-14 20:47:31 -05:00
/// Utility method to create a patch request to push an item on the end of a list
let listPush<'T> listName docId (item : 'T) =
2019-07-13 22:55:53 -05:00
let r = PatchRequest()
2019-07-14 20:47:31 -05:00
r.Script <- sprintf "this.%s.push(args.Item)" listName
2019-07-13 22:55:53 -05:00
r.Values.["Item"] <- item
2019-07-14 20:47:31 -05:00
PatchCommandData (docId, null, r, null)
2019-07-13 22:55:53 -05:00
2019-07-14 20:47:31 -05:00
/// 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)
2019-07-30 23:56:34 -05:00
/// All data manipulations within myPrayerJournal
module Data =
2019-03-30 21:08:02 -05:00
2019-07-30 23:56:34 -05:00
open Indexes
open Raven.Client.Documents.Session
2019-03-30 21:08:02 -05:00
2019-07-30 23:56:34 -05:00
/// Add a history entry
let addHistory reqId (hist : History) (sess : IAsyncDocumentSession) =
sess.Advanced.Patch<Request, History> (
RequestId.toString reqId,
(fun r -> r.history :> IEnumerable<History>),
fun (h : JavaScriptArray<History>) -> h.Add (hist) :> obj)
2019-03-30 21:08:02 -05:00
2019-07-30 23:56:34 -05:00
/// Add a note
let addNote reqId (note : Note) (sess : IAsyncDocumentSession) =
sess.Advanced.Patch<Request, Note> (
RequestId.toString reqId,
(fun r -> r.notes :> IEnumerable<Note>),
fun (h : JavaScriptArray<Note>) -> h.Add (note) :> obj)
2019-03-30 21:08:02 -05:00
2019-07-30 23:56:34 -05:00
/// Add a request
let addRequest req (sess : IAsyncDocumentSession) =
sess.StoreAsync (req, req.Id)
2019-03-30 21:08:02 -05:00
/// Retrieve all answered requests for the given user
2019-07-30 23:56:34 -05:00
let answeredRequests userId (sess : IAsyncDocumentSession) =
sess.Query<JournalRequest, Requests_AsJournal>()
2019-03-30 21:08:02 -05:00
.Where(fun r -> r.userId = userId && r.lastStatus = "Answered")
.OrderByDescending(fun r -> r.asOf)
2019-07-30 23:56:34 -05:00
.ProjectInto<JournalRequest>()
.ToListAsync()
2019-03-30 21:08:02 -05:00
/// Retrieve the user's current journal
2019-07-30 23:56:34 -05:00
let journalByUserId userId (sess : IAsyncDocumentSession) =
2019-03-30 21:08:02 -05:00
task {
2019-07-30 23:56:34 -05:00
let! jrnl =
sess.Query<JournalRequest, Requests_AsJournal>()
.Where(fun r -> r.userId = userId && r.lastStatus <> "Answered")
.OrderBy(fun r -> r.asOf)
.ProjectInto<JournalRequest>()
.ToListAsync()
return
jrnl
|> List.ofSeq
|> List.map (fun r -> r.history <- []; r.notes <- []; r)
2019-03-30 21:08:02 -05:00
}
2019-07-30 23:56:34 -05:00
/// Save changes in the current document session
let saveChanges (sess : IAsyncDocumentSession) =
sess.SaveChangesAsync ()
/// Retrieve a request, including its history and notes, by its ID and user ID
let tryFullRequestById reqId userId (sess : IAsyncDocumentSession) =
2019-03-30 21:08:02 -05:00
task {
2019-07-30 23:56:34 -05:00
let! req = RequestId.toString reqId |> sess.LoadAsync
return match Option.fromObject req with Some r when r.userId = userId -> Some r | _ -> None
2019-03-30 21:08:02 -05:00
}
2019-07-30 23:56:34 -05:00
/// Retrieve a request by its ID and user ID (without notes and history)
let tryRequestById reqId userId (sess : IAsyncDocumentSession) =
2019-03-30 21:08:02 -05:00
task {
2019-07-30 23:56:34 -05:00
match! tryFullRequestById reqId userId sess with
| Some r -> return Some { r with history = []; notes = [] }
| _ -> return None
2019-03-30 21:08:02 -05:00
}
2019-07-30 23:56:34 -05:00
/// Retrieve notes for a request by its ID and user ID
let notesById reqId userId (sess : IAsyncDocumentSession) =
2019-03-30 21:08:02 -05:00
task {
2019-07-30 23:56:34 -05:00
match! tryFullRequestById reqId userId sess with
| Some req -> return req.notes
| None -> return []
}
/// Retrieve a journal request by its ID and user ID
let tryJournalById reqId userId (sess : IAsyncDocumentSession) =
task {
let! req =
sess.Query<Request, Requests_AsJournal>()
.Where(fun x -> x.Id = (RequestId.toString reqId) && x.userId = userId)
.ProjectInto<JournalRequest>()
.FirstOrDefaultAsync ()
return Option.fromObject req
2019-03-30 21:08:02 -05:00
}
2019-07-30 23:56:34 -05:00
/// Update the recurrence for a request
let updateRecurrence reqId recurType recurCount (sess : IAsyncDocumentSession) =
sess.Advanced.Patch<Request, Recurrence> (RequestId.toString reqId, (fun r -> r.recurType), recurType)
sess.Advanced.Patch<Request, int16> (RequestId.toString reqId, (fun r -> r.recurCount), recurCount)
/// Update a snoozed request
let updateSnoozed reqId until (sess : IAsyncDocumentSession) =
sess.Advanced.Patch<Request, Ticks> (RequestId.toString reqId, (fun r -> r.snoozedUntil), until)
sess.Advanced.Patch<Request, Ticks> (RequestId.toString reqId, (fun r -> r.showAfter), until)
/// Update the "show after" timestamp for a request
let updateShowAfter reqId showAfter (sess : IAsyncDocumentSession) =
sess.Advanced.Patch<Request, Ticks> (RequestId.toString reqId, (fun r -> r.showAfter), showAfter)