diff --git a/src/MyPrayerJournal.ToPostgres/MyPrayerJournal.ToPostgres.fsproj b/src/MyPrayerJournal.ToPostgres/MyPrayerJournal.ToPostgres.fsproj new file mode 100644 index 0000000..7913b82 --- /dev/null +++ b/src/MyPrayerJournal.ToPostgres/MyPrayerJournal.ToPostgres.fsproj @@ -0,0 +1,16 @@ + + + + Exe + net7.0 + + + + + + + + + + + diff --git a/src/MyPrayerJournal.ToPostgres/Program.fs b/src/MyPrayerJournal.ToPostgres/Program.fs new file mode 100644 index 0000000..d6818ab --- /dev/null +++ b/src/MyPrayerJournal.ToPostgres/Program.fs @@ -0,0 +1,2 @@ +// For more information see https://aka.ms/fsharp-console-apps +printfn "Hello from F#" diff --git a/src/MyPrayerJournal/Data.fs b/src/MyPrayerJournal/Data.fs new file mode 100644 index 0000000..51cda0b --- /dev/null +++ b/src/MyPrayerJournal/Data.fs @@ -0,0 +1,146 @@ +module MyPrayerJournal.Data + +/// Table(s) used by myPrayerJournal +module Table = + + /// Requests + [] + let Request = "mpj.request" + + +open BitBadger.Npgsql.FSharp.Documents + +module DataConnection = + + let ensureDb () = backgroundTask { + do! Custom.nonQuery "CREATE SCHEMA IF NOT EXISTS mpj" [] + do! Definition.ensureTable Table.Request + do! Definition.ensureIndex Table.Request Optimized + } + + +/// Data access functions for requests +[] +module Request = + + open NodaTime + + /// Add a request + let add req = backgroundTask { + do! insert Table.Request (RequestId.toString req.Id) req + } + + /// Retrieve a request by its ID and user ID (includes history and notes) + let tryByIdFull reqId userId = backgroundTask { + match! Find.byId Table.Request (RequestId.toString reqId) with + | Some req when req.UserId = userId -> return Some req + | _ -> return None + } + + /// Retrieve a request by its ID and user ID (excludes history and notes) + let tryById reqId userId = backgroundTask { + match! tryByIdFull reqId userId with + | Some req -> return Some { req with History = [||]; Notes = [||] } + | None -> return None + } + + /// Does a request exist for the given request ID and user ID? + let private existsById (reqId : RequestId) (userId : UserId) = + Exists.byContains Table.Request {| Id = reqId; UserId = userId |} + + /// Update recurrence for a request + let updateRecurrence reqId userId (recurType : Recurrence) = backgroundTask { + let dbId = RequestId.toString reqId + match! existsById reqId userId with + | true -> do! Update.partialById Table.Request dbId {| Recurrence = recurType |} + | false -> invalidOp "Request ID {dbId} not found" + } + + /// Update the show-after time for a request + let updateShowAfter reqId userId (showAfter : Instant) = backgroundTask { + let dbId = RequestId.toString reqId + match! existsById reqId userId with + | true -> do! Update.partialById Table.Request dbId {| ShowAfter = showAfter |} + | false -> invalidOp "Request ID {dbId} not found" + } + + /// Update the snoozed and show-after values for a request + let updateSnoozed reqId userId (until : Instant) = backgroundTask { + let dbId = RequestId.toString reqId + match! existsById reqId userId with + | true -> do! Update.partialById Table.Request dbId {| SnoozedUntil = until; ShowAfter = until |} + | false -> invalidOp "Request ID {dbId} not found" + } + + +/// Specific manipulation of history entries +[] +module History = + + /// Add a history entry + let add reqId userId hist = backgroundTask { + let dbId = RequestId.toString reqId + match! Request.tryByIdFull reqId userId with + | Some req -> do! Update.partialById Table.Request dbId {| History = Array.append [| hist |] req.History |} + | None -> invalidOp $"Request ID {dbId} not found" + } + + +/// Data access functions for journal-style requests +[] +module Journal = + + /// Retrieve a user's answered requests + let answered userId = backgroundTask { + // TODO: only retrieve answered requests + let! reqs = Find.byContains Table.Request {| UserId = UserId.toString userId |} + return + reqs + |> Seq.ofList + |> Seq.map JournalRequest.ofRequestFull + |> Seq.filter (fun it -> it.LastStatus = Answered) + |> Seq.sortByDescending (fun it -> it.AsOf) + |> List.ofSeq + } + + /// Retrieve a user's current prayer journal (includes snoozed and non-immediate recurrence) + let forUser userId = backgroundTask { + // TODO: only retrieve unanswered requests + let! reqs = Find.byContains Table.Request {| UserId = UserId.toString userId |} + return + reqs + |> Seq.ofList + |> Seq.map JournalRequest.ofRequestFull + |> Seq.filter (fun it -> it.LastStatus = Answered) + |> Seq.sortByDescending (fun it -> it.AsOf) + |> List.ofSeq + } + + /// Does the user's journal have any snoozed requests? + let hasSnoozed userId now = backgroundTask { + let! jrnl = forUser userId + return jrnl |> List.exists (fun r -> defaultArg (r.SnoozedUntil |> Option.map (fun it -> it > now)) false) + } + + let tryById reqId userId = backgroundTask { + let! req = Request.tryById reqId userId + return req |> Option.map JournalRequest.ofRequestLite + } + + +/// Specific manipulation of note entries +[] +module Note = + + /// Add a note + let add reqId userId note = backgroundTask { + let dbId = RequestId.toString reqId + match! Request.tryByIdFull reqId userId with + | Some req -> do! Update.partialById Table.Request dbId {| Notes = Array.append [| note |] req.Notes |} + | None -> invalidOp $"Request ID {dbId} not found" + } + + /// Retrieve notes for a request by the request ID + let byRequestId reqId userId = backgroundTask { + match! Request.tryByIdFull reqId userId with Some req -> return req.Notes | None -> return [||] + } diff --git a/src/MyPrayerJournal/MyPrayerJournal.fsproj b/src/MyPrayerJournal/MyPrayerJournal.fsproj index c0f892b..3616f90 100644 --- a/src/MyPrayerJournal/MyPrayerJournal.fsproj +++ b/src/MyPrayerJournal/MyPrayerJournal.fsproj @@ -9,6 +9,7 @@ + @@ -19,6 +20,7 @@ +