Version 3.1 #71
@ -1,42 +1,11 @@
|
||||
open MyPrayerJournal.Domain
|
||||
open NodaTime
|
||||
|
||||
/// Request is the identifying record for a prayer request
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
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
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
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 =
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
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
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
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<OldRequest>("request").FindAll()
|
||||
db.GetCollection<OldRequest>("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"
|
||||
|
@ -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
|
||||
[<RequireQualifiedAccess>]
|
||||
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"
|
||||
}
|
||||
|
@ -2,8 +2,6 @@
|
||||
[<AutoOpen>]
|
||||
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
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
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
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
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
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
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.
|
||||
[<NoComparison; NoEquality>]
|
||||
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
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
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
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
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
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
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.
|
||||
[<NoComparison; NoEquality>]
|
||||
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
|
||||
}
|
||||
|
@ -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<Models.NoteEntry> ()
|
||||
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
|
||||
|
@ -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 ]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
@ -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" ]
|
||||
|
Loading…
Reference in New Issue
Block a user