From 9b85ac24126e8575274610476173fd83e3a3bbb2 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Fri, 29 Jul 2022 20:58:19 -0400 Subject: [PATCH] Convert to new data structure --- .../Program.fs | 106 ++++--- src/MyPrayerJournal/Data.fs | 38 +-- src/MyPrayerJournal/Domain.fs | 287 +++++++++--------- src/MyPrayerJournal/Handlers.fs | 38 +-- src/MyPrayerJournal/Views/Journal.fs | 6 +- src/MyPrayerJournal/Views/Request.fs | 64 ++-- 6 files changed, 281 insertions(+), 258 deletions(-) diff --git a/src/MyPrayerJournal.ConvertRecurrence/Program.fs b/src/MyPrayerJournal.ConvertRecurrence/Program.fs index 1fae3cd..4522fef 100644 --- a/src/MyPrayerJournal.ConvertRecurrence/Program.fs +++ b/src/MyPrayerJournal.ConvertRecurrence/Program.fs @@ -1,42 +1,11 @@ open MyPrayerJournal.Domain open NodaTime -/// Request is the identifying record for a prayer request -[] -type OldRequest = - { /// The ID of the request - id : RequestId - - /// The time this request was initially entered - enteredOn : Instant - - /// The ID of the user to whom this request belongs ("sub" from the JWT) - userId : UserId - - /// The time at which this request should reappear in the user's journal by manual user choice - snoozedUntil : Instant - - /// The time at which this request should reappear in the user's journal by recurrence - showAfter : Instant - - /// The type of recurrence for this request - recurType : string - - /// How many of the recurrence intervals should occur between appearances in the journal - recurCount : int16 - - /// The history entries for this request - history : History array - - /// The notes for this request - notes : Note array - } - /// The old definition of the history entry [] type OldHistory = { /// The time when this history entry was made - asOf : Instant + asOf : int64 /// The status for this history entry status : RequestAction /// The text of the update, if applicable @@ -47,12 +16,43 @@ type OldHistory = [] type OldNote = { /// The time when this note was made - asOf : Instant + asOf : int64 /// The text of the notes notes : string } +/// Request is the identifying record for a prayer request +[] +type OldRequest = + { /// The ID of the request + id : RequestId + + /// The time this request was initially entered + enteredOn : int64 + + /// The ID of the user to whom this request belongs ("sub" from the JWT) + userId : UserId + + /// The time at which this request should reappear in the user's journal by manual user choice + snoozedUntil : int64 + + /// The time at which this request should reappear in the user's journal by recurrence + showAfter : int64 + + /// The type of recurrence for this request + recurType : string + + /// How many of the recurrence intervals should occur between appearances in the journal + recurCount : int16 + + /// The history entries for this request + history : OldHistory[] + + /// The notes for this request + notes : OldNote[] + } + open LiteDB open MyPrayerJournal.Data @@ -68,27 +68,41 @@ let mapRecurrence old = | "Weeks" -> Weeks old.recurCount | _ -> Immediate +/// Convert an old history entry to the new form +let convertHistory (old : OldHistory) = + { AsOf = Instant.FromUnixTimeMilliseconds old.asOf + Status = old.status + Text = old.text + } + +/// Convert an old note to the new form +let convertNote (old : OldNote) = + { AsOf = Instant.FromUnixTimeMilliseconds old.asOf + Notes = old.notes + } + /// Map the old request to the new request let convert old = - { id = old.id - enteredOn = old.enteredOn - userId = old.userId - snoozedUntil = old.snoozedUntil - showAfter = old.showAfter - recurrence = mapRecurrence old - history = Array.toList old.history - notes = Array.toList old.notes + { Id = old.id + EnteredOn = Instant.FromUnixTimeMilliseconds old.enteredOn + UserId = old.userId + SnoozedUntil = Instant.FromUnixTimeMilliseconds old.snoozedUntil + ShowAfter = Instant.FromUnixTimeMilliseconds old.showAfter + Recurrence = mapRecurrence old + History = old.history |> Array.map convertHistory |> List.ofArray + Notes = old.notes |> Array.map convertNote |> List.ofArray } /// Remove the old request, add the converted one (removes recurType / recurCount fields) let replace (req : Request) = - db.requests.Delete(Mapping.RequestId.toBson req.id) |> ignore - db.requests.Insert(req) |> ignore - db.Checkpoint() + db.requests.Delete (Mapping.RequestId.toBson req.Id) |> ignore + db.requests.Insert req |> ignore + db.Checkpoint () -db.GetCollection("request").FindAll() +db.GetCollection("request").FindAll () |> Seq.map convert -|> Seq.iter replace +|> List.ofSeq +|> List.iter replace // For more information see https://aka.ms/fsharp-console-apps printfn "Done" diff --git a/src/MyPrayerJournal/Data.fs b/src/MyPrayerJournal/Data.fs index bf9f082..4afd347 100644 --- a/src/MyPrayerJournal/Data.fs +++ b/src/MyPrayerJournal/Data.fs @@ -4,6 +4,7 @@ open LiteDB open MyPrayerJournal open NodaTime open System.Threading.Tasks +open NodaTime.Text // fsharplint:disable MemberNames @@ -28,11 +29,14 @@ module Extensions = // It does mapping, but since we're so DU-heavy, this gives us control over the JSON representation [] module Mapping = - + + /// A NodaTime instant pattern to use for parsing instants from the database + let instantPattern = InstantPattern.CreateWithInvariantCulture "g" + /// Mapping for NodaTime's Instant type module Instant = - let fromBson (value : BsonValue) = Instant.FromUnixTimeMilliseconds value.AsInt64 - let toBson (value : Instant) : BsonValue = value.ToUnixTimeMilliseconds () + let fromBson (value : BsonValue) = (instantPattern.Parse value.AsString).Value + let toBson (value : Instant) : BsonValue = value.ToString ("g", null) /// Mapping for option types module Option = @@ -73,7 +77,7 @@ module Startup = /// Ensure the database is set up let ensureDb (db : LiteDatabase) = - db.requests.EnsureIndex (fun it -> it.userId) |> ignore + db.requests.EnsureIndex (fun it -> it.UserId) |> ignore Mapping.register () @@ -100,20 +104,20 @@ module private Helpers = /// Retrieve a request, including its history and notes, by its ID and user ID let tryFullRequestById reqId userId (db : LiteDatabase) = backgroundTask { let! req = db.requests.Find (Query.EQ ("_id", RequestId.toString reqId)) |> firstAsync - return match box req with null -> None | _ when req.userId = userId -> Some req | _ -> None + return match box req with null -> None | _ when req.UserId = userId -> Some req | _ -> None } /// Add a history entry let addHistory reqId userId hist db = backgroundTask { match! tryFullRequestById reqId userId db with - | Some req -> do! doUpdate db { req with history = hist :: req.history } + | Some req -> do! doUpdate db { req with History = hist :: req.History } | None -> invalidOp $"{RequestId.toString reqId} not found" } /// Add a note let addNote reqId userId note db = backgroundTask { match! tryFullRequestById reqId userId db with - | Some req -> do! doUpdate db { req with notes = note :: req.notes } + | Some req -> do! doUpdate db { req with Notes = note :: req.Notes } | None -> invalidOp $"{RequestId.toString reqId} not found" } @@ -129,8 +133,8 @@ let answeredRequests userId (db : LiteDatabase) = backgroundTask { return reqs |> Seq.map JournalRequest.ofRequestFull - |> Seq.filter (fun it -> it.lastStatus = Answered) - |> Seq.sortByDescending (fun it -> it.asOf) + |> Seq.filter (fun it -> it.LastStatus = Answered) + |> Seq.sortByDescending (fun it -> it.AsOf) |> List.ofSeq } @@ -140,26 +144,26 @@ let journalByUserId userId (db : LiteDatabase) = backgroundTask { return jrnl |> Seq.map JournalRequest.ofRequestLite - |> Seq.filter (fun it -> it.lastStatus <> Answered) - |> Seq.sortBy (fun it -> it.asOf) + |> Seq.filter (fun it -> it.LastStatus <> Answered) + |> Seq.sortBy (fun it -> it.AsOf) |> List.ofSeq } /// Does the user have any snoozed requests? let hasSnoozed userId now (db : LiteDatabase) = backgroundTask { let! jrnl = journalByUserId userId db - return jrnl |> List.exists (fun r -> r.snoozedUntil > now) + return jrnl |> List.exists (fun r -> r.SnoozedUntil > now) } /// Retrieve a request by its ID and user ID (without notes and history) let tryRequestById reqId userId db = backgroundTask { let! req = tryFullRequestById reqId userId db - return req |> Option.map (fun r -> { r with history = []; notes = [] }) + return req |> Option.map (fun r -> { r with History = []; Notes = [] }) } /// Retrieve notes for a request by its ID and user ID let notesById reqId userId (db : LiteDatabase) = backgroundTask { - match! tryFullRequestById reqId userId db with | Some req -> return req.notes | None -> return [] + match! tryFullRequestById reqId userId db with | Some req -> return req.Notes | None -> return [] } /// Retrieve a journal request by its ID and user ID @@ -171,20 +175,20 @@ let tryJournalById reqId userId (db : LiteDatabase) = backgroundTask { /// Update the recurrence for a request let updateRecurrence reqId userId recurType db = backgroundTask { match! tryFullRequestById reqId userId db with - | Some req -> do! doUpdate db { req with recurrence = recurType } + | Some req -> do! doUpdate db { req with Recurrence = recurType } | None -> invalidOp $"{RequestId.toString reqId} not found" } /// Update a snoozed request let updateSnoozed reqId userId until db = backgroundTask { match! tryFullRequestById reqId userId db with - | Some req -> do! doUpdate db { req with snoozedUntil = until; showAfter = until } + | Some req -> do! doUpdate db { req with SnoozedUntil = until; ShowAfter = until } | None -> invalidOp $"{RequestId.toString reqId} not found" } /// Update the "show after" timestamp for a request let updateShowAfter reqId userId showAfter db = backgroundTask { match! tryFullRequestById reqId userId db with - | Some req -> do! doUpdate db { req with showAfter = showAfter } + | Some req -> do! doUpdate db { req with ShowAfter = showAfter } | None -> invalidOp $"{RequestId.toString reqId} not found" } diff --git a/src/MyPrayerJournal/Domain.fs b/src/MyPrayerJournal/Domain.fs index 6bd8aa1..11fef56 100644 --- a/src/MyPrayerJournal/Domain.fs +++ b/src/MyPrayerJournal/Domain.fs @@ -2,8 +2,6 @@ [] module MyPrayerJournal.Domain -// fsharplint:disable RecordFieldNames - open System open Cuid open NodaTime @@ -33,12 +31,16 @@ module UserId = /// How frequently a request should reappear after it is marked "Prayed" type Recurrence = + /// A request should reappear immediately at the bottom of the list | Immediate + /// A request should reappear in the given number of hours | Hours of int16 + /// A request should reappear in the given number of days | Days of int16 + /// A request should reappear in the given number of weeks (7-day increments) | Weeks of int16 @@ -86,142 +88,6 @@ type RequestAction = | Updated | Answered - -/// History is a record of action taken on a prayer request, including updates to its text -[] -type History = - { /// The time when this history entry was made - asOf : Instant - - /// The status for this history entry - status : RequestAction - - /// The text of the update, if applicable - text : string option - } - - -/// Note is a note regarding a prayer request that does not result in an update to its text -[] -type Note = - { /// The time when this note was made - asOf : Instant - - /// The text of the notes - notes : string - } - - -/// Request is the identifying record for a prayer request -[] -type Request = - { /// The ID of the request - id : RequestId - - /// The time this request was initially entered - enteredOn : Instant - - /// The ID of the user to whom this request belongs ("sub" from the JWT) - userId : UserId - - /// The time at which this request should reappear in the user's journal by manual user choice - snoozedUntil : Instant - - /// The time at which this request should reappear in the user's journal by recurrence - showAfter : Instant - - /// The recurrence for this request - recurrence : Recurrence - - /// The history entries for this request - history : History list - - /// The notes for this request - notes : Note list - } - -/// Functions to support requests -module Request = - - /// An empty request - let empty = - { id = Cuid.generate () |> RequestId - enteredOn = Instant.MinValue - userId = UserId "" - snoozedUntil = Instant.MinValue - showAfter = Instant.MinValue - recurrence = Immediate - history = [] - notes = [] - } - - -/// JournalRequest is the form of a prayer request returned for the request journal display. It also contains -/// properties that may be filled for history and notes. -[] -type JournalRequest = - { /// The ID of the request (just the CUID part) - requestId : RequestId - - /// The ID of the user to whom the request belongs - userId : UserId - - /// The current text of the request - text : string - - /// The last time action was taken on the request - asOf : Instant - - /// The last status for the request - lastStatus : RequestAction - - /// The time that this request should reappear in the user's journal - snoozedUntil : Instant - - /// The time after which this request should reappear in the user's journal by configured recurrence - showAfter : Instant - - /// The recurrence for this request - recurrence : Recurrence - - /// History entries for the request - history : History list - - /// Note entries for the request - notes : Note list - } - -/// Functions to manipulate journal requests -module JournalRequest = - - /// Convert a request to the form used for the journal (precomputed values, no notes or history) - let ofRequestLite (req : Request) = - let hist = req.history |> List.sortByDescending (fun it -> it.asOf) |> List.tryHead - { requestId = req.id - userId = req.userId - text = req.history - |> List.filter (fun it -> Option.isSome it.text) - |> List.sortByDescending (fun it -> it.asOf) - |> List.tryHead - |> Option.map (fun h -> Option.get h.text) - |> Option.defaultValue "" - asOf = match hist with Some h -> h.asOf | None -> Instant.MinValue - lastStatus = match hist with Some h -> h.status | None -> Created - snoozedUntil = req.snoozedUntil - showAfter = req.showAfter - recurrence = req.recurrence - history = [] - notes = [] - } - - /// Same as `ofRequestLite`, but with notes and history - let ofRequestFull req = - { ofRequestLite req with - history = req.history - notes = req.notes - } - - /// Functions to manipulate request actions module RequestAction = @@ -242,11 +108,150 @@ module RequestAction = | "Answered" -> Answered | it -> invalidOp $"Bad request action {it}" + + +/// History is a record of action taken on a prayer request, including updates to its text +[] +type History = + { /// The time when this history entry was made + AsOf : Instant + + /// The status for this history entry + Status : RequestAction + + /// The text of the update, if applicable + Text : string option + } + +/// Functions to manipulate history entries +module History = + /// Determine if a history's status is `Created` - let isCreated hist = hist.status = Created + let isCreated hist = hist.Status = Created /// Determine if a history's status is `Prayed` - let isPrayed hist = hist.status = Prayed + let isPrayed hist = hist.Status = Prayed /// Determine if a history's status is `Answered` - let isAnswered hist = hist.status = Answered + let isAnswered hist = hist.Status = Answered + + +/// Note is a note regarding a prayer request that does not result in an update to its text +[] +type Note = + { /// The time when this note was made + AsOf : Instant + + /// The text of the notes + Notes : string + } + + +/// Request is the identifying record for a prayer request +[] +type Request = + { /// The ID of the request + Id : RequestId + + /// The time this request was initially entered + EnteredOn : Instant + + /// The ID of the user to whom this request belongs ("sub" from the JWT) + UserId : UserId + + /// The time at which this request should reappear in the user's journal by manual user choice + SnoozedUntil : Instant + + /// The time at which this request should reappear in the user's journal by recurrence + ShowAfter : Instant + + /// The recurrence for this request + Recurrence : Recurrence + + /// The history entries for this request + History : History list + + /// The notes for this request + Notes : Note list + } + +/// Functions to support requests +module Request = + + /// An empty request + let empty = + { Id = Cuid.generate () |> RequestId + EnteredOn = Instant.MinValue + UserId = UserId "" + SnoozedUntil = Instant.MinValue + ShowAfter = Instant.MinValue + Recurrence = Immediate + History = [] + Notes = [] + } + + +/// JournalRequest is the form of a prayer request returned for the request journal display. It also contains +/// properties that may be filled for history and notes. +[] +type JournalRequest = + { /// The ID of the request (just the CUID part) + RequestId : RequestId + + /// The ID of the user to whom the request belongs + UserId : UserId + + /// The current text of the request + Text : string + + /// The last time action was taken on the request + AsOf : Instant + + /// The last status for the request + LastStatus : RequestAction + + /// The time that this request should reappear in the user's journal + SnoozedUntil : Instant + + /// The time after which this request should reappear in the user's journal by configured recurrence + ShowAfter : Instant + + /// The recurrence for this request + Recurrence : Recurrence + + /// History entries for the request + History : History list + + /// Note entries for the request + Notes : Note list + } + +/// Functions to manipulate journal requests +module JournalRequest = + + /// Convert a request to the form used for the journal (precomputed values, no notes or history) + let ofRequestLite (req : Request) = + let hist = req.History |> List.sortByDescending (fun it -> it.AsOf) |> List.tryHead + { RequestId = req.Id + UserId = req.UserId + Text = req.History + |> List.filter (fun it -> Option.isSome it.Text) + |> List.sortByDescending (fun it -> it.AsOf) + |> List.tryHead + |> Option.map (fun h -> Option.get h.Text) + |> Option.defaultValue "" + AsOf = match hist with Some h -> h.AsOf | None -> Instant.MinValue + LastStatus = match hist with Some h -> h.Status | None -> Created + SnoozedUntil = req.SnoozedUntil + ShowAfter = req.ShowAfter + Recurrence = req.Recurrence + History = [] + Notes = [] + } + + /// Same as `ofRequestLite`, but with notes and history + let ofRequestFull req = + { ofRequestLite req with + History = req.History + Notes = req.Notes + } diff --git a/src/MyPrayerJournal/Handlers.fs b/src/MyPrayerJournal/Handlers.fs index e8eee7d..16fc0b0 100644 --- a/src/MyPrayerJournal/Handlers.fs +++ b/src/MyPrayerJournal/Handlers.fs @@ -237,7 +237,7 @@ module Components = let journalItems : HttpHandler = requiresAuthentication Error.notAuthorized >=> fun next ctx -> backgroundTask { let now = now ctx let! jrnl = Data.journalByUserId (userId ctx) (db ctx) - let shown = jrnl |> List.filter (fun it -> now > it.snoozedUntil && now > it.showAfter) + let shown = jrnl |> List.filter (fun it -> now > it.SnoozedUntil && now > it.ShowAfter) return! renderComponent [ Views.Journal.journalItems now shown ] next ctx } @@ -332,9 +332,9 @@ module Request = match! Data.tryRequestById reqId usrId db with | Some req -> let now = now ctx - do! Data.addHistory reqId usrId { asOf = now; status = Prayed; text = None } db + do! Data.addHistory reqId usrId { AsOf = now; Status = Prayed; Text = None } db let nextShow = - match Recurrence.duration req.recurrence with + match Recurrence.duration req.Recurrence with | 0L -> Instant.MinValue | duration -> now.Plus (Duration.FromSeconds duration) do! Data.updateShowAfter reqId usrId nextShow db @@ -351,7 +351,7 @@ module Request = match! Data.tryRequestById reqId usrId db with | Some _ -> let! notes = ctx.BindFormAsync () - do! Data.addNote reqId usrId { asOf = now ctx; notes = notes.notes } db + do! Data.addNote reqId usrId { AsOf = now ctx; Notes = notes.notes } db do! db.saveChanges () return! (withSuccessMessage "Added Notes" >=> hideModal "notes" >=> created) next ctx | None -> return! Error.notFound next ctx @@ -367,7 +367,7 @@ module Request = let snoozed : HttpHandler = requiresAuthentication Error.notAuthorized >=> fun next ctx -> backgroundTask { let! reqs = Data.journalByUserId (userId ctx) (db ctx) let now = now ctx - let snoozed = reqs |> List.filter (fun it -> it.snoozedUntil > now) + let snoozed = reqs |> List.filter (fun it -> it.SnoozedUntil > now) return! partial "Active Requests" (Views.Request.snoozed now snoozed) next ctx } @@ -444,14 +444,14 @@ module Request = let now = now ctx let req = { Request.empty with - userId = usrId - enteredOn = now - showAfter = Instant.MinValue - recurrence = parseRecurrence form - history = [ - { asOf = now - status = Created - text = Some form.requestText + UserId = usrId + EnteredOn = now + ShowAfter = Instant.MinValue + Recurrence = parseRecurrence form + History = [ + { AsOf = now + Status = Created + Text = Some form.requestText } ] } @@ -470,18 +470,18 @@ module Request = | Some req -> // update recurrence if changed let recur = parseRecurrence form - match recur = req.recurrence with + match recur = req.Recurrence with | true -> () | false -> - do! Data.updateRecurrence req.requestId usrId recur db + do! Data.updateRecurrence req.RequestId usrId recur db match recur with - | Immediate -> do! Data.updateShowAfter req.requestId usrId Instant.MinValue db + | Immediate -> do! Data.updateShowAfter req.RequestId usrId Instant.MinValue db | _ -> () // append history let upd8Text = form.requestText.Trim () - let text = match upd8Text = req.text with true -> None | false -> Some upd8Text - do! Data.addHistory req.requestId usrId - { asOf = now ctx; status = (Option.get >> RequestAction.ofString) form.status; text = text } db + let text = match upd8Text = req.Text with true -> None | false -> Some upd8Text + do! Data.addHistory req.RequestId usrId + { AsOf = now ctx; Status = (Option.get >> RequestAction.ofString) form.status; Text = text } db do! db.saveChanges () let nextUrl = match form.returnTo with diff --git a/src/MyPrayerJournal/Views/Journal.fs b/src/MyPrayerJournal/Views/Journal.fs index ec2163b..09d38ae 100644 --- a/src/MyPrayerJournal/Views/Journal.fs +++ b/src/MyPrayerJournal/Views/Journal.fs @@ -8,7 +8,7 @@ open MyPrayerJournal /// Display a card for this prayer request let journalCard now req = - let reqId = RequestId.toString req.requestId + let reqId = RequestId.toString req.RequestId let spacer = span [] [ rawText " " ] div [ _class "col" ] [ div [ _class "card h-100" ] [ @@ -45,10 +45,10 @@ let journalCard now req = ] ] div [ _class "card-body" ] [ - p [ _class "request-text" ] [ str req.text ] + p [ _class "request-text" ] [ str req.Text ] ] div [ _class "card-footer text-end text-muted px-1 py-0" ] [ - em [] [ str "last activity "; relativeDate req.asOf now ] + em [] [ str "last activity "; relativeDate req.AsOf now ] ] ] ] diff --git a/src/MyPrayerJournal/Views/Request.fs b/src/MyPrayerJournal/Views/Request.fs index 7ece0f5..472a615 100644 --- a/src/MyPrayerJournal/Views/Request.fs +++ b/src/MyPrayerJournal/Views/Request.fs @@ -8,10 +8,10 @@ open NodaTime /// Create a request within the list let reqListItem now req = - let reqId = RequestId.toString req.requestId - let isAnswered = req.lastStatus = Answered - let isSnoozed = req.snoozedUntil > now - let isPending = (not isSnoozed) && req.showAfter > now + let reqId = RequestId.toString req.RequestId + let isAnswered = req.LastStatus = Answered + let isSnoozed = req.SnoozedUntil > now + let isPending = (not isSnoozed) && req.ShowAfter > now let btnClass = _class "btn btn-light mx-2" let restoreBtn (link : string) title = button [ btnClass; _hxPatch $"/request/{reqId}/{link}"; _title title ] [ icon "restore" ] @@ -23,13 +23,13 @@ let reqListItem now req = if isSnoozed then restoreBtn "cancel-snooze" "Cancel Snooze" elif isPending then restoreBtn "show" "Show Now" p [ _class "request-text mb-0" ] [ - str req.text + str req.Text if isSnoozed || isPending || isAnswered then br [] small [ _class "text-muted" ] [ - if isSnoozed then [ str "Snooze expires "; relativeDate req.snoozedUntil now ] - elif isPending then [ str "Request appears next "; relativeDate req.showAfter now ] - else (* isAnswered *) [ str "Answered "; relativeDate req.asOf now ] + if isSnoozed then [ str "Snooze expires "; relativeDate req.SnoozedUntil now ] + elif isPending then [ str "Request appears next "; relativeDate req.ShowAfter now ] + else (* isAnswered *) [ str "Answered "; relativeDate req.AsOf now ] |> em [] ] ] @@ -74,27 +74,27 @@ let snoozed now reqs = let full (clock : IClock) (req : Request) = let now = clock.GetCurrentInstant () let answered = - req.history - |> List.filter RequestAction.isAnswered + req.History + |> List.filter History.isAnswered |> List.tryHead - |> Option.map (fun x -> x.asOf) - let prayed = (req.history |> List.filter RequestAction.isPrayed |> List.length).ToString "N0" + |> Option.map (fun x -> x.AsOf) + let prayed = (req.History |> List.filter History.isPrayed |> List.length).ToString "N0" let daysOpen = let asOf = defaultArg answered now - ((asOf - (req.history |> List.filter RequestAction.isCreated |> List.head).asOf).TotalDays |> int).ToString "N0" + ((asOf - (req.History |> List.filter History.isCreated |> List.head).AsOf).TotalDays |> int).ToString "N0" let lastText = - req.history - |> List.filter (fun h -> Option.isSome h.text) - |> List.sortByDescending (fun h -> h.asOf) - |> List.map (fun h -> Option.get h.text) + req.History + |> List.filter (fun h -> Option.isSome h.Text) + |> List.sortByDescending (fun h -> h.AsOf) + |> List.map (fun h -> Option.get h.Text) |> List.head // The history log including notes (and excluding the final entry for answered requests) let log = - let toDisp (h : History) = {| asOf = h.asOf; text = h.text; status = RequestAction.toString h.status |} + let toDisp (h : History) = {| asOf = h.AsOf; text = h.Text; status = RequestAction.toString h.Status |} let all = - req.notes - |> List.map (fun n -> {| asOf = n.asOf; text = Some n.notes; status = "Notes" |}) - |> List.append (req.history |> List.map toDisp) + req.Notes + |> List.map (fun n -> {| asOf = n.AsOf; text = Some n.Notes; status = "Notes" |}) + |> List.append (req.History |> List.map toDisp) |> List.sortByDescending (fun it -> it.asOf) // Skip the first entry for answered requests; that info is already displayed match answered with Some _ -> all |> List.skip 1 | None -> all @@ -139,7 +139,7 @@ let edit (req : JournalRequest) returnTo isNew = | "snoozed" -> "/requests/snoozed" | _ (* "journal" *) -> "/journal" let recurCount = - match req.recurrence with + match req.Recurrence with | Immediate -> None | Hours h -> Some h | Days d -> Some d @@ -154,7 +154,7 @@ let edit (req : JournalRequest) returnTo isNew = "/request" |> match isNew with true -> _hxPost | false -> _hxPatch ] [ input [ _type "hidden" _name "requestId" - _value (match isNew with true -> "new" | false -> RequestId.toString req.requestId) ] + _value (match isNew with true -> "new" | false -> RequestId.toString req.RequestId) ] input [ _type "hidden"; _name "returnTo"; _value returnTo ] div [ _class "form-floating pb-3" ] [ textarea [ _id "requestText" @@ -162,7 +162,7 @@ let edit (req : JournalRequest) returnTo isNew = _class "form-control" _style "min-height: 8rem;" _placeholder "Enter the text of the request" - _autofocus; _required ] [ str req.text ] + _autofocus; _required ] [ str req.Text ] label [ _for "requestText" ] [ str "Prayer Request" ] ] br [] @@ -202,7 +202,7 @@ let edit (req : JournalRequest) returnTo isNew = _name "recurType" _value "Immediate" _onclick "mpj.edit.toggleRecurrence(event)" - match req.recurrence with Immediate -> _checked | _ -> () ] + match req.Recurrence with Immediate -> _checked | _ -> () ] label [ _for "rI" ] [ str "Immediately" ] ] div [ _class "form-check mx-2"] [ @@ -212,7 +212,7 @@ let edit (req : JournalRequest) returnTo isNew = _name "recurType" _value "Other" _onclick "mpj.edit.toggleRecurrence(event)" - match req.recurrence with Immediate -> () | _ -> _checked ] + match req.Recurrence with Immediate -> () | _ -> _checked ] label [ _for "rO" ] [ rawText "Every…" ] ] div [ _class "form-floating mx-2"] [ @@ -224,7 +224,7 @@ let edit (req : JournalRequest) returnTo isNew = _value recurCount _style "width:6rem;" _required - match req.recurrence with Immediate -> _disabled | _ -> () ] + match req.Recurrence with Immediate -> _disabled | _ -> () ] label [ _for "recurCount" ] [ str "Count" ] ] div [ _class "form-floating mx-2" ] [ @@ -233,14 +233,14 @@ let edit (req : JournalRequest) returnTo isNew = _name "recurInterval" _style "width:6rem;" _required - match req.recurrence with Immediate -> _disabled | _ -> () ] [ - option [ _value "Hours"; match req.recurrence with Hours _ -> _selected | _ -> () ] [ + match req.Recurrence with Immediate -> _disabled | _ -> () ] [ + option [ _value "Hours"; match req.Recurrence with Hours _ -> _selected | _ -> () ] [ str "hours" ] - option [ _value "Days"; match req.recurrence with Days _ -> _selected | _ -> () ] [ + option [ _value "Days"; match req.Recurrence with Days _ -> _selected | _ -> () ] [ str "days" ] - option [ _value "Weeks"; match req.recurrence with Weeks _ -> _selected | _ -> () ] [ + option [ _value "Weeks"; match req.Recurrence with Weeks _ -> _selected | _ -> () ] [ str "weeks" ] ] @@ -259,7 +259,7 @@ let edit (req : JournalRequest) returnTo isNew = /// Display a list of notes for a request let notes now notes = let toItem (note : Note) = - p [] [ small [ _class "text-muted" ] [ relativeDate note.asOf now ]; br []; str note.notes ] + p [] [ small [ _class "text-muted" ] [ relativeDate note.AsOf now ]; br []; str note.Notes ] [ p [ _class "text-center" ] [ strong [] [ str "Prior Notes for This Request" ] ] match notes with | [] -> p [ _class "text-center text-muted" ] [ str "There are no prior notes for this request" ]