myPrayerJournal v2 #27
@ -4,6 +4,130 @@ open FSharp.Control.Tasks.V2.ContextInsensitive
|
|||||||
open Microsoft.EntityFrameworkCore
|
open Microsoft.EntityFrameworkCore
|
||||||
open Microsoft.FSharpLu
|
open Microsoft.FSharpLu
|
||||||
|
|
||||||
|
open Newtonsoft.Json
|
||||||
|
open Raven.Client.Documents.Indexes
|
||||||
|
open System
|
||||||
|
open System.Collections.Generic
|
||||||
|
|
||||||
|
/// JSON converter for request IDs
|
||||||
|
type RequestIdJsonConverter() =
|
||||||
|
inherit JsonConverter<RequestId>()
|
||||||
|
override __.WriteJson(writer : JsonWriter, value : RequestId, _ : JsonSerializer) =
|
||||||
|
(string >> writer.WriteValue) value
|
||||||
|
override __.ReadJson(reader: JsonReader, _ : Type, _ : RequestId, _ : bool, _ : JsonSerializer) =
|
||||||
|
(string >> RequestId.fromIdString) reader.Value
|
||||||
|
|
||||||
|
|
||||||
|
/// JSON converter for user IDs
|
||||||
|
type UserIdJsonConverter() =
|
||||||
|
inherit JsonConverter<UserId>()
|
||||||
|
override __.WriteJson(writer : JsonWriter, value : UserId, _ : JsonSerializer) =
|
||||||
|
(string >> writer.WriteValue) value
|
||||||
|
override __.ReadJson(reader: JsonReader, _ : Type, _ : UserId, _ : bool, _ : JsonSerializer) =
|
||||||
|
(string >> UserId) reader.Value
|
||||||
|
|
||||||
|
|
||||||
|
/// JSON converter for Ticks
|
||||||
|
type TicksJsonConverter() =
|
||||||
|
inherit JsonConverter<Ticks>()
|
||||||
|
override __.WriteJson(writer : JsonWriter, value : Ticks, _ : JsonSerializer) =
|
||||||
|
writer.WriteValue (value.toLong ())
|
||||||
|
override __.ReadJson(reader: JsonReader, _ : Type, _ : Ticks, _ : bool, _ : JsonSerializer) =
|
||||||
|
(string >> int64 >> Ticks) reader.Value
|
||||||
|
|
||||||
|
/// Index episodes by their series Id
|
||||||
|
type Requests_ByUserId () as this =
|
||||||
|
inherit AbstractJavaScriptIndexCreationTask ()
|
||||||
|
do
|
||||||
|
this.Maps <- HashSet<string> [ "map('Requests', function (req) { return { userId : req.userId } })" ]
|
||||||
|
|
||||||
|
|
||||||
|
/// Extensions on the IAsyncDocumentSession interface to support our data manipulation needs
|
||||||
|
[<AutoOpen>]
|
||||||
|
module Extensions =
|
||||||
|
|
||||||
|
open Raven.Client.Documents.Commands.Batches
|
||||||
|
open Raven.Client.Documents.Operations
|
||||||
|
open Raven.Client.Documents.Session
|
||||||
|
open System
|
||||||
|
|
||||||
|
/// Format an RQL query by a strongly-typed index
|
||||||
|
let fromIndex (typ : Type) =
|
||||||
|
typ.Name.Replace ("_", "/") |> sprintf "from index '%s'"
|
||||||
|
|
||||||
|
/// Utility method to create patch requests
|
||||||
|
let createPatch<'T> collName itemId (item : 'T) =
|
||||||
|
let r = PatchRequest()
|
||||||
|
r.Script <- sprintf "this.%s.push(args.Item)" collName
|
||||||
|
r.Values.["Item"] <- item
|
||||||
|
PatchCommandData (itemId, null, r, null)
|
||||||
|
|
||||||
|
// Extensions for the RavenDB session type
|
||||||
|
type IAsyncDocumentSession with
|
||||||
|
|
||||||
|
/// Add a history entry
|
||||||
|
member this.AddHistory (reqId : RequestId) (hist : History) =
|
||||||
|
createPatch "history" (string reqId) hist
|
||||||
|
|> this.Advanced.Defer
|
||||||
|
|
||||||
|
/// Add a request
|
||||||
|
member this.AddRequest req =
|
||||||
|
this.StoreAsync (req, req.Id)
|
||||||
|
|
||||||
|
/// Retrieve all answered requests for the given user
|
||||||
|
// TODO: not right
|
||||||
|
member this.AnsweredRequests (userId : UserId) =
|
||||||
|
sprintf "%s where userId = '%s' and lastStatus = 'Answered' order by asOf as long desc"
|
||||||
|
(fromIndex typeof<Requests_ByUserId>) (string userId)
|
||||||
|
|> this.Advanced.AsyncRawQuery<JournalRequest>
|
||||||
|
|
||||||
|
/// Retrieve the user's current journal
|
||||||
|
// TODO: probably not right either
|
||||||
|
member this.JournalByUserId (userId : UserId) =
|
||||||
|
sprintf "%s where userId = '%s' and lastStatus <> 'Answered' order by showAfter as long"
|
||||||
|
(fromIndex typeof<Requests_ByUserId>) (string userId)
|
||||||
|
|> this.Advanced.AsyncRawQuery<JournalRequest>
|
||||||
|
|
||||||
|
/// Retrieve a request by its ID and user ID
|
||||||
|
member this.TryRequestById (reqId : RequestId) userId =
|
||||||
|
task {
|
||||||
|
let! req = this.LoadAsync (string reqId)
|
||||||
|
match Option.fromObject req with
|
||||||
|
| Some r when r.userId = userId -> return Some r
|
||||||
|
| _ -> return None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve notes for a request by its ID and user ID
|
||||||
|
member this.NotesById reqId userId =
|
||||||
|
task {
|
||||||
|
match! this.TryRequestById reqId userId with
|
||||||
|
| Some _ -> return this.Notes.AsNoTracking().Where(fun n -> n.requestId = reqId) |> List.ofSeq
|
||||||
|
| None -> return []
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a journal request by its ID and user ID
|
||||||
|
member this.TryJournalById reqId userId =
|
||||||
|
task {
|
||||||
|
let! req = this.Journal.FirstOrDefaultAsync(fun r -> r.requestId = reqId && r.userId = userId)
|
||||||
|
return Option.fromObject req
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a request, including its history and notes, by its ID and user ID
|
||||||
|
member this.TryFullRequestById requestId userId =
|
||||||
|
task {
|
||||||
|
match! this.TryJournalById requestId userId with
|
||||||
|
| Some req ->
|
||||||
|
let! fullReq =
|
||||||
|
this.Requests.AsNoTracking()
|
||||||
|
.Include(fun r -> r.history)
|
||||||
|
.Include(fun r -> r.notes)
|
||||||
|
.FirstOrDefaultAsync(fun r -> r.requestId = requestId && r.userId = userId)
|
||||||
|
match Option.fromObject fullReq with
|
||||||
|
| Some _ -> return Some { req with history = List.ofSeq fullReq.history; notes = List.ofSeq fullReq.notes }
|
||||||
|
| None -> return None
|
||||||
|
| None -> return None
|
||||||
|
}
|
||||||
|
|
||||||
/// Entity Framework configuration for myPrayerJournal
|
/// Entity Framework configuration for myPrayerJournal
|
||||||
module internal EFConfig =
|
module internal EFConfig =
|
||||||
|
|
||||||
|
158
src/MyPrayerJournal.Api/Domain.fs
Normal file
158
src/MyPrayerJournal.Api/Domain.fs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
[<AutoOpen>]
|
||||||
|
/// The data model for myPrayerJournal
|
||||||
|
module MyPrayerJournal.Domain
|
||||||
|
|
||||||
|
/// A Collision-resistant Unique IDentifier
|
||||||
|
type Cuid =
|
||||||
|
| Cuid of string
|
||||||
|
with
|
||||||
|
/// The string value of the CUID
|
||||||
|
override x.ToString () = match x with Cuid y -> y
|
||||||
|
|
||||||
|
|
||||||
|
/// Request ID is a CUID
|
||||||
|
type RequestId =
|
||||||
|
| RequestId of Cuid
|
||||||
|
with
|
||||||
|
/// The string representation of the request ID
|
||||||
|
override x.ToString () = match x with RequestId y -> (string >> sprintf "Requests/%s") y
|
||||||
|
/// Create a request ID from a string representation
|
||||||
|
static member fromIdString (y : string) = (Cuid >> RequestId) <| y.Replace("Requests/", "")
|
||||||
|
|
||||||
|
|
||||||
|
/// User ID is a string (the "sub" part of the JWT)
|
||||||
|
type UserId =
|
||||||
|
| UserId of string
|
||||||
|
with
|
||||||
|
/// The string representation of the user ID
|
||||||
|
override x.ToString () = match x with UserId y -> y
|
||||||
|
|
||||||
|
|
||||||
|
/// A long integer representing seconds since the epoch
|
||||||
|
type Ticks =
|
||||||
|
| Ticks of int64
|
||||||
|
with
|
||||||
|
/// The int64 (long) representation of ticks
|
||||||
|
member x.toLong () = match x with Ticks y -> y
|
||||||
|
|
||||||
|
|
||||||
|
/// How frequently a request should reappear after it is marked "Prayed"
|
||||||
|
type Recurrence =
|
||||||
|
| Immediate
|
||||||
|
| Hours
|
||||||
|
| Days
|
||||||
|
| Weeks
|
||||||
|
with
|
||||||
|
/// The string reprsentation used in the database and the web app
|
||||||
|
override x.ToString () =
|
||||||
|
match x with
|
||||||
|
| Immediate -> "immediate"
|
||||||
|
| Hours -> "hours"
|
||||||
|
| Days -> "days"
|
||||||
|
| Weeks -> "weeks"
|
||||||
|
/// Create a recurrence value from a string
|
||||||
|
static member fromString x =
|
||||||
|
match x with
|
||||||
|
| "immediate" -> Immediate
|
||||||
|
| "hours" -> Hours
|
||||||
|
| "days" -> Days
|
||||||
|
| "weeks" -> Weeks
|
||||||
|
| _ -> invalidOp (sprintf "%s is not a valid recurrence" x)
|
||||||
|
|
||||||
|
|
||||||
|
/// 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 : Ticks
|
||||||
|
/// The status for this history entry
|
||||||
|
status : string
|
||||||
|
/// The text of the update, if applicable
|
||||||
|
text : string option
|
||||||
|
}
|
||||||
|
with
|
||||||
|
/// An empty history entry
|
||||||
|
static member empty =
|
||||||
|
{ asOf = Ticks 0L
|
||||||
|
status = ""
|
||||||
|
text = None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 : Ticks
|
||||||
|
/// The text of the notes
|
||||||
|
notes : string
|
||||||
|
}
|
||||||
|
with
|
||||||
|
/// An empty note
|
||||||
|
static member empty =
|
||||||
|
{ asOf = Ticks 0L
|
||||||
|
notes = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request is the identifying record for a prayer request
|
||||||
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
|
type Request =
|
||||||
|
{ /// The ID of the request
|
||||||
|
Id : string
|
||||||
|
/// The time this request was initially entered
|
||||||
|
enteredOn : Ticks
|
||||||
|
/// 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 : Ticks
|
||||||
|
/// The time at which this request should reappear in the user's journal by recurrence
|
||||||
|
showAfter : Ticks
|
||||||
|
/// The type of recurrence for this request
|
||||||
|
recurType : Recurrence
|
||||||
|
/// How many of the recurrence intervals should occur between appearances in the journal
|
||||||
|
recurCount : int16
|
||||||
|
/// The history entries for this request
|
||||||
|
history : History list
|
||||||
|
/// The notes for this request
|
||||||
|
notes : Note list
|
||||||
|
}
|
||||||
|
with
|
||||||
|
/// An empty request
|
||||||
|
static member empty =
|
||||||
|
{ Id = ""
|
||||||
|
enteredOn = Ticks 0L
|
||||||
|
userId = UserId ""
|
||||||
|
snoozedUntil = Ticks 0L
|
||||||
|
showAfter = Ticks 0L
|
||||||
|
recurType = Immediate
|
||||||
|
recurCount = 0s
|
||||||
|
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
|
||||||
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
|
type JournalRequest =
|
||||||
|
{ /// The ID of the request
|
||||||
|
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 : Ticks
|
||||||
|
/// The last status for the request
|
||||||
|
lastStatus : string
|
||||||
|
/// The time that this request should reappear in the user's journal
|
||||||
|
snoozedUntil : Ticks
|
||||||
|
/// The time after which this request should reappear in the user's journal by configured recurrence
|
||||||
|
showAfter : Ticks
|
||||||
|
/// The type of recurrence for this request
|
||||||
|
recurType : Recurrence
|
||||||
|
/// How many of the recurrence intervals should occur between appearances in the journal
|
||||||
|
recurCount : int16
|
||||||
|
/// History entries for the request
|
||||||
|
history : History list
|
||||||
|
/// Note entries for the request
|
||||||
|
notes : Note list
|
||||||
|
}
|
@ -40,6 +40,7 @@ module Error =
|
|||||||
module private Helpers =
|
module private Helpers =
|
||||||
|
|
||||||
open Microsoft.AspNetCore.Http
|
open Microsoft.AspNetCore.Http
|
||||||
|
open Raven.Client.Documents
|
||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
open System.Security.Claims
|
open System.Security.Claims
|
||||||
|
|
||||||
@ -47,6 +48,10 @@ module private Helpers =
|
|||||||
let db (ctx : HttpContext) =
|
let db (ctx : HttpContext) =
|
||||||
ctx.GetService<AppDbContext> ()
|
ctx.GetService<AppDbContext> ()
|
||||||
|
|
||||||
|
/// Create a RavenDB session
|
||||||
|
let session (ctx : HttpContext) =
|
||||||
|
ctx.GetService<IDocumentStore>().OpenAsyncSession ()
|
||||||
|
|
||||||
/// Get the user's "sub" claim
|
/// Get the user's "sub" claim
|
||||||
let user (ctx : HttpContext) =
|
let user (ctx : HttpContext) =
|
||||||
ctx.User.Claims |> Seq.tryFind (fun u -> u.Type = ClaimTypes.NameIdentifier)
|
ctx.User.Claims |> Seq.tryFind (fun u -> u.Type = ClaimTypes.NameIdentifier)
|
||||||
@ -54,7 +59,7 @@ module private Helpers =
|
|||||||
/// Get the current user's ID
|
/// Get the current user's ID
|
||||||
// NOTE: this may raise if you don't run the request through the authorize handler first
|
// NOTE: this may raise if you don't run the request through the authorize handler first
|
||||||
let userId ctx =
|
let userId ctx =
|
||||||
((user >> Option.get) ctx).Value
|
((user >> Option.get) ctx).Value |> UserId
|
||||||
|
|
||||||
/// Return a 201 CREATED response
|
/// Return a 201 CREATED response
|
||||||
let created next ctx =
|
let created next ctx =
|
||||||
@ -163,28 +168,28 @@ module Request =
|
|||||||
>=> fun next ctx ->
|
>=> fun next ctx ->
|
||||||
task {
|
task {
|
||||||
let! r = ctx.BindJsonAsync<Models.Request> ()
|
let! r = ctx.BindJsonAsync<Models.Request> ()
|
||||||
let db = db ctx
|
use sess = session ctx
|
||||||
let reqId = Cuid.Generate ()
|
let reqId = (Cuid.Generate >> Domain.Cuid >> RequestId) ()
|
||||||
let usrId = userId ctx
|
let usrId = userId ctx
|
||||||
let now = jsNow ()
|
let now = (jsNow >> Ticks) ()
|
||||||
{ Request.empty with
|
do! sess.AddRequest
|
||||||
requestId = reqId
|
{ Request.empty with
|
||||||
userId = usrId
|
Id = string reqId
|
||||||
enteredOn = now
|
userId = usrId
|
||||||
showAfter = now
|
enteredOn = now
|
||||||
recurType = r.recurType
|
showAfter = now
|
||||||
recurCount = r.recurCount
|
recurType = Recurrence.fromString r.recurType
|
||||||
}
|
recurCount = r.recurCount
|
||||||
|> db.AddEntry
|
history = [
|
||||||
{ History.empty with
|
{ History.empty with
|
||||||
requestId = reqId
|
asOf = now
|
||||||
asOf = now
|
status = "Created"
|
||||||
status = "Created"
|
text = Some r.requestText
|
||||||
text = Some r.requestText
|
}
|
||||||
}
|
]
|
||||||
|> db.AddEntry
|
}
|
||||||
let! _ = db.SaveChangesAsync ()
|
do! sess.SaveChangesAsync ()
|
||||||
match! db.TryJournalById reqId usrId with
|
match! sess.TryJournalById reqId usrId with
|
||||||
| Some req -> return! (setStatusCode 201 >=> json req) next ctx
|
| Some req -> return! (setStatusCode 201 >=> json req) next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
@ -194,23 +199,23 @@ module Request =
|
|||||||
authorize
|
authorize
|
||||||
>=> fun next ctx ->
|
>=> fun next ctx ->
|
||||||
task {
|
task {
|
||||||
let db = db ctx
|
use sess = session ctx
|
||||||
match! db.TryRequestById reqId (userId ctx) with
|
let reqId = (Domain.Cuid >> RequestId) reqId
|
||||||
|
match! sess.TryRequestById reqId (userId ctx) with
|
||||||
| Some req ->
|
| Some req ->
|
||||||
let! hist = ctx.BindJsonAsync<Models.HistoryEntry> ()
|
let! hist = ctx.BindJsonAsync<Models.HistoryEntry> ()
|
||||||
let now = jsNow ()
|
let now = (jsNow >> Ticks) ()
|
||||||
{ History.empty with
|
{ History.empty with
|
||||||
requestId = reqId
|
asOf = now
|
||||||
asOf = now
|
status = hist.status
|
||||||
status = hist.status
|
text = match hist.updateText with null | "" -> None | x -> Some x
|
||||||
text = match hist.updateText with null | "" -> None | x -> Some x
|
|
||||||
}
|
}
|
||||||
|> db.AddEntry
|
|> sess.AddHistory reqId
|
||||||
match hist.status with
|
match hist.status with
|
||||||
| "Prayed" ->
|
| "Prayed" ->
|
||||||
db.UpdateEntry { req with showAfter = now + (recurrence.[req.recurType] * int64 req.recurCount) }
|
sess.UpdateEntry { req with showAfter = now + (recurrence.[req.recurType] * int64 req.recurCount) }
|
||||||
| _ -> ()
|
| _ -> ()
|
||||||
let! _ = db.SaveChangesAsync ()
|
do! sess.SaveChangesAsync ()
|
||||||
return! created next ctx
|
return! created next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
@ -225,9 +230,8 @@ module Request =
|
|||||||
| Some _ ->
|
| Some _ ->
|
||||||
let! notes = ctx.BindJsonAsync<Models.NoteEntry> ()
|
let! notes = ctx.BindJsonAsync<Models.NoteEntry> ()
|
||||||
{ Note.empty with
|
{ Note.empty with
|
||||||
requestId = reqId
|
asOf = (jsNow >> Ticks) ()
|
||||||
asOf = jsNow ()
|
notes = notes.notes
|
||||||
notes = notes.notes
|
|
||||||
}
|
}
|
||||||
|> db.AddEntry
|
|> db.AddEntry
|
||||||
let! _ = db.SaveChangesAsync ()
|
let! _ = db.SaveChangesAsync ()
|
||||||
@ -248,7 +252,8 @@ module Request =
|
|||||||
authorize
|
authorize
|
||||||
>=> fun next ctx ->
|
>=> fun next ctx ->
|
||||||
task {
|
task {
|
||||||
match! (db ctx).TryJournalById reqId (userId ctx) with
|
use sess = session ctx
|
||||||
|
match! sess.TryJournalById reqId (userId ctx) with
|
||||||
| Some req -> return! json req next ctx
|
| Some req -> return! json req next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
@ -258,7 +263,8 @@ module Request =
|
|||||||
authorize
|
authorize
|
||||||
>=> fun next ctx ->
|
>=> fun next ctx ->
|
||||||
task {
|
task {
|
||||||
match! (db ctx).TryFullRequestById reqId (userId ctx) with
|
use sess = session ctx
|
||||||
|
match! sess.TryFullRequestById reqId (userId ctx) with
|
||||||
| Some req -> return! json req next ctx
|
| Some req -> return! json req next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
@ -281,7 +287,7 @@ module Request =
|
|||||||
match! db.TryRequestById reqId (userId ctx) with
|
match! db.TryRequestById reqId (userId ctx) with
|
||||||
| Some req ->
|
| Some req ->
|
||||||
let! show = ctx.BindJsonAsync<Models.Show> ()
|
let! show = ctx.BindJsonAsync<Models.Show> ()
|
||||||
{ req with showAfter = show.showAfter }
|
{ req with showAfter = Ticks show.showAfter }
|
||||||
|> db.UpdateEntry
|
|> db.UpdateEntry
|
||||||
let! _ = db.SaveChangesAsync ()
|
let! _ = db.SaveChangesAsync ()
|
||||||
return! setStatusCode 204 next ctx
|
return! setStatusCode 204 next ctx
|
||||||
@ -297,7 +303,7 @@ module Request =
|
|||||||
match! db.TryRequestById reqId (userId ctx) with
|
match! db.TryRequestById reqId (userId ctx) with
|
||||||
| Some req ->
|
| Some req ->
|
||||||
let! until = ctx.BindJsonAsync<Models.SnoozeUntil> ()
|
let! until = ctx.BindJsonAsync<Models.SnoozeUntil> ()
|
||||||
{ req with snoozedUntil = until.until; showAfter = until.until }
|
{ req with snoozedUntil = Ticks until.until; showAfter = Ticks until.until }
|
||||||
|> db.UpdateEntry
|
|> db.UpdateEntry
|
||||||
let! _ = db.SaveChangesAsync ()
|
let! _ = db.SaveChangesAsync ()
|
||||||
return! setStatusCode 204 next ctx
|
return! setStatusCode 204 next ctx
|
||||||
@ -313,7 +319,7 @@ module Request =
|
|||||||
match! db.TryRequestById reqId (userId ctx) with
|
match! db.TryRequestById reqId (userId ctx) with
|
||||||
| Some req ->
|
| Some req ->
|
||||||
let! recur = ctx.BindJsonAsync<Models.Recurrence> ()
|
let! recur = ctx.BindJsonAsync<Models.Recurrence> ()
|
||||||
{ req with recurType = recur.recurType; recurCount = recur.recurCount }
|
{ req with recurType = Recurrence.fromString recur.recurType; recurCount = recur.recurCount }
|
||||||
|> db.UpdateEntry
|
|> db.UpdateEntry
|
||||||
let! _ = db.SaveChangesAsync ()
|
let! _ = db.SaveChangesAsync ()
|
||||||
return! setStatusCode 204 next ctx
|
return! setStatusCode 204 next ctx
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Domain.fs" />
|
||||||
<Compile Include="Data.fs" />
|
<Compile Include="Data.fs" />
|
||||||
<Compile Include="Handlers.fs" />
|
<Compile Include="Handlers.fs" />
|
||||||
<Compile Include="Program.fs" />
|
<Compile Include="Program.fs" />
|
||||||
@ -20,6 +21,7 @@
|
|||||||
<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.10.29" />
|
<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.10.29" />
|
||||||
<PackageReference Include="NCuid.NetCore" Version="1.0.1" />
|
<PackageReference Include="NCuid.NetCore" Version="1.0.1" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.2.0" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.2.0" />
|
||||||
|
<PackageReference Include="RavenDb.Client" Version="4.2.1" />
|
||||||
<PackageReference Include="TaskBuilder.fs" Version="2.1.0" />
|
<PackageReference Include="TaskBuilder.fs" Version="2.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@ -31,8 +33,4 @@
|
|||||||
<Folder Include="wwwroot\" />
|
<Folder Include="wwwroot\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MyPrayerJournal.Domain\MyPrayerJournal.Domain.fsproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,114 +0,0 @@
|
|||||||
[<AutoOpen>]
|
|
||||||
/// Entities for use in the data model for myPrayerJournal
|
|
||||||
module MyPrayerJournal.Entities
|
|
||||||
|
|
||||||
open System.Collections.Generic
|
|
||||||
|
|
||||||
/// Type alias for a Collision-resistant Unique IDentifier
|
|
||||||
type Cuid = string
|
|
||||||
|
|
||||||
/// Request ID is a CUID
|
|
||||||
type RequestId = Cuid
|
|
||||||
|
|
||||||
/// User ID is a string (the "sub" part of the JWT)
|
|
||||||
type UserId = string
|
|
||||||
|
|
||||||
/// History is a record of action taken on a prayer request, including updates to its text
|
|
||||||
type [<CLIMutable; NoComparison; NoEquality>] History =
|
|
||||||
{ /// The ID of the request to which this history entry applies
|
|
||||||
requestId : RequestId
|
|
||||||
/// The time when this history entry was made
|
|
||||||
asOf : int64
|
|
||||||
/// The status for this history entry
|
|
||||||
status : string
|
|
||||||
/// The text of the update, if applicable
|
|
||||||
text : string option
|
|
||||||
}
|
|
||||||
with
|
|
||||||
/// An empty history entry
|
|
||||||
static member empty =
|
|
||||||
{ requestId = ""
|
|
||||||
asOf = 0L
|
|
||||||
status = ""
|
|
||||||
text = None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Note is a note regarding a prayer request that does not result in an update to its text
|
|
||||||
and [<CLIMutable; NoComparison; NoEquality>] Note =
|
|
||||||
{ /// The ID of the request to which this note applies
|
|
||||||
requestId : RequestId
|
|
||||||
/// The time when this note was made
|
|
||||||
asOf : int64
|
|
||||||
/// The text of the notes
|
|
||||||
notes : string
|
|
||||||
}
|
|
||||||
with
|
|
||||||
/// An empty note
|
|
||||||
static member empty =
|
|
||||||
{ requestId = ""
|
|
||||||
asOf = 0L
|
|
||||||
notes = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Request is the identifying record for a prayer request
|
|
||||||
and [<CLIMutable; NoComparison; NoEquality>] Request =
|
|
||||||
{ /// The ID of the request
|
|
||||||
requestId : 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 : string
|
|
||||||
/// 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 : ICollection<History>
|
|
||||||
/// The notes for this request
|
|
||||||
notes : ICollection<Note>
|
|
||||||
}
|
|
||||||
with
|
|
||||||
/// An empty request
|
|
||||||
static member empty =
|
|
||||||
{ requestId = ""
|
|
||||||
enteredOn = 0L
|
|
||||||
userId = ""
|
|
||||||
snoozedUntil = 0L
|
|
||||||
showAfter = 0L
|
|
||||||
recurType = "immediate"
|
|
||||||
recurCount = 0s
|
|
||||||
history = List<History> ()
|
|
||||||
notes = List<Note> ()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
[<CLIMutable; NoComparison; NoEquality>]
|
|
||||||
type JournalRequest =
|
|
||||||
{ /// The ID of the request
|
|
||||||
requestId : RequestId
|
|
||||||
/// The ID of the user to whom the request belongs
|
|
||||||
userId : string
|
|
||||||
/// The current text of the request
|
|
||||||
text : string
|
|
||||||
/// The last time action was taken on the request
|
|
||||||
asOf : int64
|
|
||||||
/// The last status for the request
|
|
||||||
lastStatus : string
|
|
||||||
/// The time that this request should reappear in the user's journal
|
|
||||||
snoozedUntil : int64
|
|
||||||
/// The time after which this request should reappear in the user's journal by configured 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
|
|
||||||
/// History entries for the request
|
|
||||||
history : History list
|
|
||||||
/// Note entries for the request
|
|
||||||
notes : Note list
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Entities.fs" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
Loading…
Reference in New Issue
Block a user